setwd("/shared/ifbstor1/projects/dubii2021/djarrige/m3-stat-R/mini-projet")
Loaded required libraries
libraries
knitr
FactoMineR
factoextra
gprofiler2
pheatmap
dplyr
ClassDiscovery
biomaRt

Synopsis du projet

Travail demandé

Le but de ce travail est de mettre en oeuvre les méthodes vues dans le module 3 “R et statistiques” pour explorer le jeu de données de Pavkovic, et de rendre un rapport d’analyse au format .Rmd.

Nous fournissons ci-dessous une trame avec les principales sections attendues. Certaines contiennent déjà du code. Vous devrez en compléter d’autres. Sentez-vous libres d’adapter cette trame ou d’y ajouter des analyses complémentaires si elles vous aident à interpréter vos résultats.

Remise du rapport

Date: le 26 mai 2021 à minuit. Si vous anticipez un problème pour remettre le rapport à cette date contactez-nous aussi rapidement que possible pour que nous puissions prévoir une remise plus tardive.

  • Commencez par renommer le fichier .Rmd en remplaçant Prenom-NOM par vos nom et prénom.

  • Le rapport est attendu en formats .Rmd + .HTML (en gardant l’option self_contained de l’en-tête activée).

  • Déposez les fichiers dans un sous-dossier de vote compte du cluster. Attention, veillez à respecter précisément cette structure de chemin car nous nous baserons dessus pour récupérer vos résultats.

    /shared/projects/dubii2021/[login]/m3-stat-R/mini-projet

Critères d’évaluation

  • Reproductibilité des analyses: nous tenterons de regénérer le rapport HTML à partir de votre Rmd, en partant de notre compte sur le serveur IFB.
  • Manipulation des objets R
  • Mobilisation des méthodes statistiques vues au cours
  • Pertinence des interprétations statistiques
  • Pertinence des interprétations biologiques
  • Clarté de la rédaction
  • Clarté des illustrations (figures et tableaux): graphismes, légendes …

Nous vous encourageons à assurer la lisibilité de votre code (syntaxe, nommage des variables, commentaires de code)

Objectifs scientifiques

Nous partons du même jeu de données Fil Rouge de ce module issues de la publication Pavkovic, M., Pantano, L., Gerlach, C.V. et al. Multi omics analysis of fibrotic kidneys in two mouse models. Sci Data 6, 92 (2019). https://doi.org/10.1038/s41597-019-0095-5

Rappel sur les échantillons:

Deux modèles de fibrose rénale chez la souris sont étudiés:

  1. Le premier est un modèle de néphropathie réversible induite par l’acide folique (folic acid (FA)). Les souris ont été sacrifiées avant le traitement (normal), puis à jour 1, 2, 7 et 14 (day1,…) après une seule injection d’acide folique.

  2. Le second est un modèle irréversible induit chrirurgicalement (unilateral ureteral obstruction (UUO)). les souris ont été sacrifiées avant obstruction (day 0) et à 3, 7 et 14 jours après obstruction par ligation de l’uretère du rein gauche.

A partir de ces extraits de rein, l’ARN messager total et les petits ARNs ont été séquencés et les protéines caratérisées par spectrométrie de masse en tandem (TMT).

But scientifique: Dans le tutoriel sur les dataframes, vous avez travaillé sur les données de transcriptome du modèle UUO. Dans ce mini-projet, vous allez travailler sur les données du transcriptome du modèle FA afin de regrouper les observations (échantillon) et les gènes selon des profils d’expression similaires.

Votre projet se décompose en 5 parties dont 3 seront à réaliser par vous:

  1. Statitiques descriptives des données brutes: commandes fournies
  2. Normalisation des données : commandes fournies
  3. Statistiques descriptives des données normalisées: à vous de jouer
  4. Analyse de regroupement des données: à vous de jouer
  5. Analyse d’enrichissement fonctionnel: à vous de jouer

1. Les données brutes

Vous n’avez rien à coder ici. Le code est fourni.

Chargement des données brutes

Le bloc suivant contient une fonction qui permet de télécharger un fichier dans l’espace de travail, sauf s’il est déjà présent. Nous l’utiliserons ensuite pour télécharger les données à analyser en évitant de refaire le transfert à chaque exécution de l’analyse.

#' @title Download a file only if it is not yet here
#' @author Jacques van Helden email{Jacques.van-Helden@@france-bioinformatique.fr}
#' @param url_base base of the URL, that will be prepended to the file name
#' @param file_name name of the file (should not contain any path)
#' @param local_folder path of a local folder where the file should be stored
#' @return the function returns the path of the local file, built from local_folder and file_name
#' @export©
download_only_once <- function(
  url_base, 
  file_name,
  local_folder) {

  ## Define the source URL  
  url <- file.path(url_base, file_name)
  message("Source URL\n\t",  url)

  ## Define the local file
  local_file <- file.path(local_folder, file_name)
  
  ## Create the local data folder if it does not exist
  dir.create(local_folder, showWarnings = FALSE, recursive = TRUE)
  
  ## Download the file ONLY if it is not already there
  if (!file.exists(local_file)) {
    message("Downloading file from source URL to local file\n\t", 
            local_file)
    download.file(url = url, destfile = local_file)
  } else {
    message("Local file already exists, no need to download\n\t", 
            local_file)
  }
  
  return(local_file)
}

Nous téléchargeons deux fichiers dans un dossier local ~/m3-stat-R/pavkovic_analysis (vous pouvez changer le nom ou chemin dans le chunk ci-dessous), et les chargeons dans les data.frames suivants:

  • Données brutes de transcriptome: fa_expr_raw
  • Métadonnées: fa_meta
## Define the remote URL and local folder
pavkovic_url <- "https://github.com/DU-Bii/module-3-Stat-R/raw/master/stat-R_2021/data/pavkovic_2019/"

## Define the local folder for this analysis (where the data will be downloaded and the results generated)
pavkovic_folder <- "~/m3-stat-R/pavkovic_analysis"

## Define a sub-folder for the data
pavkovic_data_folder <- file.path(pavkovic_folder, "data")

## Download and load the expression data table
## Note: we use check.names=FALSE to avoid replacing hyphens by dots
## in sample names, because we want to keep them as in the 
## original data files. 
message("Downloading FA transcriptome file\t", "fa_raw_counts.tsv.gz",
  "\n\tfrom\t", pavkovic_url)
fa_expr_file <- download_only_once(
  url_base = pavkovic_url, 
  file_name = "fa_raw_counts.tsv.gz",
  local_folder = pavkovic_data_folder)

## Load the expresdsion table
message("Loading FA transcriptome data from\n\t", fa_expr_file)
fa_expr_raw <- read.delim(file = fa_expr_file, 
                       header = TRUE, 
                       row.names = 1)

## Download the metadata file
message("Downloading FA metadata file\t", "fa_transcriptome_metadata.tsv",
  "\n\tfrom\t", pavkovic_url)
fa_meta_file <- download_only_once(
  url_base = pavkovic_url, 
  file_name = "fa_transcriptome_metadata.tsv",
  local_folder = pavkovic_data_folder)

## Load the metadata
message("Loading FA metadata from\n\t", fa_meta_file)
fa_meta <- read.delim(file = fa_meta_file, 
                       header = TRUE, 
                       row.names = 1)

Nous regardons la structure de chaque dataframe.

str(fa_expr_raw)
'data.frame':   46679 obs. of  18 variables:
 $ day1_1  : num  2278.8 0 36.3 13.2 0 ...
 $ day1_2  : num  1786.5 0 22.15 7.15 27.9 ...
 $ day1_3  : num  2368.62 0 39.48 1.12 6.9 ...
 $ day14_1 : num  627.758 0 14.471 0.867 5.692 ...
 $ day14_2 : num  559.2 0 10.2 0 1.9 ...
 $ day14_3 : num  611.434 0 31.691 0 0.655 ...
 $ day2_1  : num  2145.22 0 300.56 1.71 57.38 ...
 $ day2_2  : num  262.45 0 4.77 0 0 ...
 $ day2_3  : num  745.84 0 123.9 5.26 38.9 ...
 $ day3_1  : num  987.185 0 51.856 0.802 8.931 ...
 $ day3_2  : num  1077.65 0 8.43 0 6.97 ...
 $ day3_3  : num  1335.1 0 69.9 0 0 ...
 $ day7_1  : num  1096.08 0 6.67 0 7.94 ...
 $ day7_2  : num  1035.846 0 6.955 0.849 101.648 ...
 $ day7_3  : num  1090.04 0 42.58 1.71 0.65 ...
 $ normal_1: num  483.23 0 7.35 0.86 32.06 ...
 $ normal_2: num  1842.1 0 11.2 0 10.4 ...
 $ normal_3: num  475.7 0 1.03 0 0 ...
str(fa_meta)
'data.frame':   18 obs. of  5 variables:
 $ dataType    : chr  "transcriptome" "transcriptome" "transcriptome" "transcriptome" ...
 $ sampleName  : chr  "day14_1" "day14_2" "day14_3" "day1_1" ...
 $ condition   : chr  "day14" "day14" "day14" "day1" ...
 $ sampleNumber: int  1 2 3 1 2 3 1 2 3 1 ...
 $ color       : chr  "#FF4400" "#FF4400" "#FF4400" "#BBD7FF" ...

Les deux fichiers ne donnent pas les observations de l’échantillon dans le même ordre:

fa_meta$sampleName == names(fa_expr_raw)
 [1] FALSE FALSE FALSE FALSE FALSE FALSE  TRUE  TRUE  TRUE  TRUE  TRUE  TRUE  TRUE  TRUE  TRUE  TRUE  TRUE  TRUE

Nous les réorganisons les échantillons dans l’ordre de l’expérience: condition normale, puis day 1 à 14 avec les 3 réplicats.

sample_order <- c(paste(rep(c("normal", "day1", "day2", "day3", "day7", "day14"), each = 3),
                        1:3, sep = "_"))

fa_expr_raw <- fa_expr_raw[,sample_order]
fa_meta <- fa_meta[match(sample_order, fa_meta$sampleName),]

# View(fa_meta)
kable(fa_meta, caption = "Metdata for Pavkovoc FA transcriptome")
Metdata for Pavkovoc FA transcriptome
dataType sampleName condition sampleNumber color
16 transcriptome normal_1 normal 1 #BBFFBB
17 transcriptome normal_2 normal 2 #BBFFBB
18 transcriptome normal_3 normal 3 #BBFFBB
4 transcriptome day1_1 day1 1 #BBD7FF
5 transcriptome day1_2 day1 2 #BBD7FF
6 transcriptome day1_3 day1 3 #BBD7FF
7 transcriptome day2_1 day2 1 #F0BBFF
8 transcriptome day2_2 day2 2 #F0BBFF
9 transcriptome day2_3 day2 3 #F0BBFF
10 transcriptome day3_1 day3 1 #FFFFDD
11 transcriptome day3_2 day3 2 #FFFFDD
12 transcriptome day3_3 day3 3 #FFFFDD
13 transcriptome day7_1 day7 1 #FFDD88
14 transcriptome day7_2 day7 2 #FFDD88
15 transcriptome day7_3 day7 3 #FFDD88
1 transcriptome day14_1 day14 1 #FF4400
2 transcriptome day14_2 day14 2 #FF4400
3 transcriptome day14_3 day14 3 #FF4400

=> Ainsi, nous avons un jeu de données avec un échantillon de 18 observations et des données d’expression de 46679 gènes.

Statistiques descriptives

Dans le tutorial sur les dataframes sur le jeu de données “uuo” (relisez le corrigé), nous vous avons demandé de créer un data.frame qui collecte les statistiques par gène et par échantillon. Nous vous demandons de réaliser une étude similaire sur les données “FA” avant et après normalisation inter-échantillons des données. Le code de la partie avant normalisation est donné.

Par échantillon avant normalisation

Nous créons un data.frame nommé sample_stat_prenorm qui comporte une ligne par échantillon et une colonne par statistique. Nous calculons les statistiques suivantes sur les valeurs log2 d’expression de chaque échantillon:

  • moyenne
  • écart-type
  • intervalle inter-quartiles
  • premier quartile
  • médiane
  • troisième quartile
  • maximum
  • nombre de valeurs nulles

Il est affiché avec la fonction kable().

message("Computing sample-wise statistics on raw counts")
sample_stat_prenorm <- data.frame(
  mean = apply(fa_expr_raw, 2, mean, na.rm = TRUE),
  sd = apply(fa_expr_raw, 2, sd, na.rm = TRUE),
  iqr = apply(fa_expr_raw, 2, IQR, na.rm = TRUE),
  Q1 = apply(fa_expr_raw, 2, quantile, p = 0.25, na.rm = TRUE),
  median = apply(fa_expr_raw, 2, median, na.rm = TRUE),
  Q3 = apply(fa_expr_raw, 2, quantile, p = 0.75, na.rm = TRUE),
  max = apply(fa_expr_raw, 2, max, na.rm = TRUE),
  null = apply(fa_expr_raw == 0, 2, sum, na.rm = TRUE)
)

kable(sample_stat_prenorm, caption = "Sample-wise statistics before normalisation.")
Sample-wise statistics before normalisation.
mean sd iqr Q1 median Q3 max null
normal_1 576.2992 57643.81 46.38417 0 1.166694 46.38417 11777395 21415
normal_2 1172.5615 111509.82 103.13749 0 2.975840 103.13749 22658521 20203
normal_3 531.8620 57203.96 41.80724 0 1.081680 41.80724 11636735 21660
day1_1 663.0580 65219.36 58.76393 0 1.105540 58.76393 13174979 21700
day1_2 1224.5149 123140.72 111.71166 0 3.053561 111.71166 25118595 20152
day1_3 1368.6144 133450.04 122.38187 0 3.766666 122.38187 27255572 19621
day2_1 1161.7628 103598.87 127.04744 0 3.501480 127.04744 21036079 19978
day2_2 223.8844 20431.97 14.54242 0 0.000000 14.54242 4037963 24446
day2_3 628.4406 57480.66 63.36112 0 1.478198 63.36112 11662570 21455
day3_1 649.8389 55210.88 77.84371 0 2.076066 77.84371 11170233 20772
day3_2 514.8649 42768.34 74.16479 0 1.941705 74.16479 8612320 21142
day3_3 854.0997 80565.29 104.95124 0 2.975772 104.95124 16484449 20287
day7_1 576.2437 53112.82 90.51017 0 2.256164 90.51017 10690493 20865
day7_2 510.2741 47373.18 80.57478 0 2.537673 80.57478 9682527 20467
day7_3 604.9701 59072.56 83.50019 0 2.874706 83.50019 12164986 20045
day14_1 540.2640 54481.09 65.01270 0 2.025707 65.01270 11178198 20496
day14_2 681.9987 74825.35 69.05744 0 2.032894 69.05744 15430799 20870
day14_3 562.5697 56026.62 75.11681 0 2.247271 75.11681 11575718 20396

Par gène avant normalisation

Nous créons ci-dessous un data.frame nommé gene_stat_prenorm qui comporte une ligne par gène et une colonne par statistique. Nous calculons les statistiques suivantes sur les valeurs log2 de chaque gène.

  • moyenne
  • médiane
  • écart-type
  • premier quartile
  • troisième quartile
  • maximum
  • nombre de valeurs nulles
  • intervalle inter-quartiles

Ces résultats sont stockés dans un data.frame avec 1 ligne par échantillon et 1 colonne par statistique. Nous affichons les lignes 100 à 109 de ce tableau de statistiques avec la fonction kable().

## Gene-wise statistics for the raw counts (will be used for normalisation)
message("Computing gene-wise statistics on raw counts")
gene_stat_prenorm <- data.frame(
  mean = apply(fa_expr_raw, 1, mean, na.rm = TRUE),
  sd = apply(fa_expr_raw, 1, sd, na.rm = TRUE),
  iqr = apply(fa_expr_raw, 1, IQR, na.rm = TRUE),
  Q1 = apply(fa_expr_raw, 1, quantile, p = 0.25, na.rm = TRUE),
  median = apply(fa_expr_raw, 1, median, na.rm = TRUE),
  Q3 = apply(fa_expr_raw, 1, quantile, p = 0.75, na.rm = TRUE),
  max = apply(fa_expr_raw, 1, max, na.rm = TRUE),
  null = apply(fa_expr_raw == 0, 1, sum, na.rm = TRUE)
)

kable(gene_stat_prenorm[100:109, ], caption = "Gene-wise statistics before normalisation")
Gene-wise statistics before normalisation
mean sd iqr Q1 median Q3 max null
ENSMUSG00000000567 320.336744 286.399084 364.362298 91.046357 261.659973 455.40866 1052.68945 0
ENSMUSG00000000568 857.348825 694.432189 282.978949 575.571199 711.096105 858.55015 3406.45030 0
ENSMUSG00000000579 422.075702 162.290415 157.732606 340.417017 447.572742 498.14962 728.51737 0
ENSMUSG00000000581 498.978227 248.612029 259.324338 320.599085 471.269224 579.92342 942.51104 0
ENSMUSG00000000594 2113.558334 1237.521694 937.571105 1400.226182 1868.718762 2337.79729 6408.96044 0
ENSMUSG00000000600 2299.978163 911.222891 1480.192582 1719.874980 2093.572929 3200.06756 3674.18811 0
ENSMUSG00000000605 325.552118 169.456624 195.879561 221.735904 282.608205 417.61546 791.31256 0
ENSMUSG00000000606 9.929258 32.151350 2.736779 1.178311 1.934744 3.91509 138.48677 4
ENSMUSG00000000617 18.804116 9.470819 13.654701 12.188276 18.150391 25.84298 36.00235 0
ENSMUSG00000000627 15.623306 14.341852 12.308127 4.780913 10.064895 17.08904 46.29752 0

2. Filtrage et normalisation des données

Vous n’avez rien à coder ici. Le code est fourni.

Il existe plusieurs façons de normaliser les données de transcriptome vues dans les modules 4 et 5 (cf. total counts, quantiles, TMM, RLE, limma voom,…), mais nous avons choisi ici une solution simple tout en étant robuste pour normaliser les données en standardisant le 3ème quantile.

La méthode choisie ici consiste à :

  1. Ecarter les gènes “non-détectés”, c’est-à-dire ceux ayant des valeurs nulles dans au moins 90% des échantillons.

  2. Ecarter les gènes à peine exprimés, c’est-à-dire ceux ayant une valeur moyenne < 10 (arbitrairement).

  3. Standardiser les échantillons sur le 3ème quartile des gènes restants: on divise les comptages bruts par le 3ème quartile de l’échantillon et on multiplie par le 3ème quartile de l’ensemble des échantillons.

  4. Normaliser les comptages (au sens propre, c’est-à-dire rapprocher leur distribution de la distribution gaussienne) par une transformation logarithmique (log2).

Nous fournissons ci-dessous le code.

Filtrage : élimination des gènes non détectés ou à peine exprimés

## Data filtering: genes having at least 90% null values
message("Filtering undetected genes")
undetected_genes <- gene_stat_prenorm$null >= ncol(fa_expr_raw) * 0.9
print(paste0("Undetected genes (null in >= 90% samples): ", sum(undetected_genes)))
[1] "Undetected genes (null in >= 90% samples): 14380"
## Data filtering: genes having a mean expression < 10
message("Filtering barely expressed genes")
barely_expressed_genes <- gene_stat_prenorm$mean < 10
print(paste0("Barely expressed genes (mean < 10): ", sum(barely_expressed_genes)))
[1] "Barely expressed genes (mean < 10): 26286"
## Apply filtering on both criteria
discarded_genes <- undetected_genes | barely_expressed_genes
print(paste0("Discarded genes: ", sum(discarded_genes)))
[1] "Discarded genes: 26288"
kept_genes <- !discarded_genes
print(paste0("Kept genes: ", sum(kept_genes)))
[1] "Kept genes: 20391"
## Genes after filtering
fa_expr_filtered <- fa_expr_raw[kept_genes, ]

Standardisation entre échantillons

Nous appliquons ici une méthode simple mais efficace de standardisation en appliquant un facteur multiplicatif qui ramène tous les échantillons au même troisième quartile (\(Q3\)).

#### Inter-sample standardisation on the Q3 of raw counts ####
total_q3 <- quantile(unlist(fa_expr_filtered), probs = 0.75)
sample_stat_prenorm$Q3_filterd <- apply(fa_expr_filtered, 2, quantile, probs = 0.75)
sample_stat_prenorm$scale_factor <- 1 / sample_stat_prenorm$Q3_filterd * total_q3

## Apply standardisation
fa_expr_standard <- t(t(fa_expr_filtered) * unlist(sample_stat_prenorm$scale_factor))
## Check 3rd quantile after standardisation
kable(apply(fa_expr_standard, 2, quantile, probs = 0.75), 
      col.names = "Q3_standardised", 
      caption = "Third quartile of the filtered counts after inter-sample standardisation of the third quartiles. ")
Third quartile of the filtered counts after inter-sample standardisation of the third quartiles.
Q3_standardised
normal_1 364.9332
normal_2 364.9332
normal_3 364.9332
day1_1 364.9332
day1_2 364.9332
day1_3 364.9332
day2_1 364.9332
day2_2 364.9332
day2_3 364.9332
day3_1 364.9332
day3_2 364.9332
day3_3 364.9332
day7_1 364.9332
day7_2 364.9332
day7_3 364.9332
day14_1 364.9332
day14_2 364.9332
day14_3 364.9332
#boxplot(fa_expr_standard, horizontal = TRUE)

Transformation log2

Nous appliquons une transformation en log2 des données brutes, après avoir ajouté un epsilon \(\epsilon = 1\) (les valeurs nulles seront donc représentées par un log2(counts) valant \(0\). Nous stockons le résultat dans un data.frame nommé fa_expr_log2.

Nous affichons un fragment des tableaux fa_expr_raw et fa_expr_log2 en sélectionnant les lignes 100 à 109 et les colonnes 5 à 10, afin de nous assurer que la transformation en log2 a bien fonctionné.

## Log2 transformation of the transcriptome data
epsilon <- 1
fa_expr_log2 <- log2(fa_expr_standard + epsilon)
# dim(fa_expr_log2)
# View(head(fa_expr_log2))

## Display of a fragment of the data before and after log2 transformation
kable(fa_expr_raw[100:109, 5:10], caption = "Fragment des données transcriptomiques brutes")
Fragment des données transcriptomiques brutes
day1_2 day1_3 day2_1 day2_2 day2_3 day3_1
ENSMUSG00000000567 599.648075 304.093177 1052.689447 106.995584 347.13842 479.59911
ENSMUSG00000000568 1008.026306 1349.126716 818.257157 116.136417 3406.45030 766.09722
ENSMUSG00000000579 599.832586 500.735597 473.399472 36.258005 410.57787 347.05054
ENSMUSG00000000581 942.511039 744.646735 546.260344 87.788535 319.12679 461.45732
ENSMUSG00000000594 3063.845705 2743.404374 2283.051270 1115.588250 1491.61384 1576.68451
ENSMUSG00000000600 3341.854530 3561.805180 3674.188108 589.840068 1399.10751 3446.01856
ENSMUSG00000000605 791.312561 558.710943 489.579657 77.008213 256.00200 282.80077
ENSMUSG00000000606 4.785252 6.566374 3.970399 0.000000 138.48677 0.00000
ENSMUSG00000000617 15.839486 29.138902 30.228506 6.670500 18.27493 13.41152
ENSMUSG00000000627 36.673777 35.967747 46.297517 2.878275 17.03638 15.45854
kable(fa_expr_log2[100:109, 5:10], caption = "Fragment des données transcriptomiques après transformation log2")
Fragment des données transcriptomiques après transformation log2
day1_2 day1_3 day2_1 day2_2 day2_3 day3_1
ENSMUSG00000000711 9.709367 9.861557 9.287726 12.335149 9.755002 10.013232
ENSMUSG00000000724 5.904466 6.218882 4.464461 5.782181 5.701477 5.803482
ENSMUSG00000000731 5.203571 5.404039 4.963615 5.970908 4.299021 4.505637
ENSMUSG00000000732 5.398925 8.209062 2.126857 3.751910 6.948597 4.970987
ENSMUSG00000000738 8.520431 8.430289 8.263532 7.909466 8.250468 7.455809
ENSMUSG00000000740 11.228789 11.320488 11.117466 10.630164 11.595716 10.156658
ENSMUSG00000000743 7.882486 7.268069 8.646853 10.469226 6.135917 7.385994
ENSMUSG00000000751 7.302103 8.404643 7.851161 6.677037 8.189071 7.123055
ENSMUSG00000000753 0.000000 2.885227 2.310527 0.000000 0.000000 4.605696
ENSMUSG00000000759 8.296064 8.620921 7.830522 9.377892 9.333861 10.038888

3. Statistiques descriptives sur les données normalisées

A vous de jouer!

Statistiques par gène après normalisation

Générez un data.frame nommée gene_stat_norm avec une ligne par gène à partir du tableau de données normalisées, avec les statistiques suivantes (une statistique par colonne):

  • moyenne
  • variance
  • écart-type
  • coefficient de variation (écart-type divisé par la moyenne)
  • intervalle inter-quartiles
  • minimum
  • médiane
  • maximum
## Gene-wise statistics after normalisation
message("Computing gene-wise statistics on log2-transformed and normalised counts")
gene_stat_norm <- data.frame(mean = apply(fa_expr_log2, 1, mean),
                             var = apply(fa_expr_log2, 1, var),
                             sd = apply(fa_expr_log2, 1, sd),
                             iqr = apply(fa_expr_log2, 1, IQR),
                             min = apply(fa_expr_log2, 1, min),
                             med = apply(fa_expr_log2, 1, median),
                             max = apply(fa_expr_log2, 1, max))

# Ajout du coefficient de variation
gene_stat_norm$coef_var <- (gene_stat_norm$sd / gene_stat_norm$mean)

kable(gene_stat_norm[100:109,], caption = "Statistiques par gène sur les données normalisées")
Statistiques par gène sur les données normalisées
mean var sd iqr min med max coef_var
ENSMUSG00000000711 9.871818 0.5175315 0.7193966 0.3385336 9.222232 9.732185 12.335149 0.0728738
ENSMUSG00000000724 5.626582 0.1705859 0.4130204 0.2736887 4.464461 5.727916 6.218882 0.0734052
ENSMUSG00000000731 4.971927 0.5455394 0.7386064 1.1354431 3.683158 4.824454 6.203856 0.1485554
ENSMUSG00000000732 5.590982 2.3882878 1.5454086 2.3926755 2.126857 5.661715 8.209062 0.2764109
ENSMUSG00000000738 8.442163 0.2524164 0.5024106 0.4350922 7.455809 8.329340 9.540606 0.0595121
ENSMUSG00000000740 10.803785 0.1537509 0.3921108 0.6194127 10.156658 10.757275 11.595716 0.0362938
ENSMUSG00000000743 7.595448 1.0151685 1.0075557 0.7874255 6.135917 7.327031 10.469226 0.1326526
ENSMUSG00000000751 7.397915 0.4442613 0.6665293 0.5886229 6.387679 7.283686 9.002489 0.0900969
ENSMUSG00000000753 2.836140 2.9644420 1.7217555 1.5150652 0.000000 3.152627 5.192750 0.6070771
ENSMUSG00000000759 8.601708 0.3046843 0.5519821 0.4823724 7.830522 8.491647 10.038888 0.0641712

Annotation des gènes

Chaque gène étant donné par son identifiant dans la base de données ENSEMBL vous utiliserez le paquet biomaRt de bioconductor pour ajouter des annotations : symbole, chromosome, coordonnées génomiques, brin. Suivez pas à pas la méthode proposée (certaines étapes peuvent prendre quelques minutes):

  • chargez le paquet biomaRt, voire installer-le uniquement si nécessaire. Indiquez le code à l’emplacement adéquat dans le .Rmd.

  • sélectionnez la base de données ENSEMBL avec la fonction useMart(). Attention à choisir le bon génome avec l’agument dataset: “mmusculus_gene_ensembl”

  • avec la fonction getBM() récupérez de la base de données ENSEMBL les champs demandés (pour symbole utilisez external_gene_name) en appliquant “ensembl_geneid” pour l’agument filter et en indiquant pour l’argument values le vecteur des identifiants des gènes présents dans le dataframe gene_stat_norm. Vous obtenez un dataframe.

A présent, ajoutez au dataframe gene_stat_norm en 1ères colonnes les annotations retrouvées grâce à biomaRt. Attention, certains gènes ne sont pas retrouvés dans la version d’ENSEMBL sur biomaRt donc laissez des NA comme données manquantes dans ce cas. Nous vous recommandons d’utiliser la function merge() de R base ou bien left_join() de dplyr pour fusionner les deux dataframes en un seul.

### Gene annotations ####
message("Getting gene annotations")

# Connecting to Ensembl Biomart database and selecting mouse dataset
ensembl <- useMart("ensembl", dataset="mmusculus_gene_ensembl")

# Let's take a look a the names of our desired attributes in the dataset
kable(listAttributes(ensembl)[1:15,])
name description page
ensembl_gene_id Gene stable ID feature_page
ensembl_gene_id_version Gene stable ID version feature_page
ensembl_transcript_id Transcript stable ID feature_page
ensembl_transcript_id_version Transcript stable ID version feature_page
ensembl_peptide_id Protein stable ID feature_page
ensembl_peptide_id_version Protein stable ID version feature_page
ensembl_exon_id Exon stable ID feature_page
description Gene description feature_page
chromosome_name Chromosome/scaffold name feature_page
start_position Gene start (bp) feature_page
end_position Gene end (bp) feature_page
strand Strand feature_page
band Karyotype band feature_page
transcript_start Transcript start (bp) feature_page
transcript_end Transcript end (bp) feature_page
# Let's look at the filters list to pick the desired filter
kable(listFilters(ensembl)[45:50,])
name description
45 with_uniprotsptrembl With UniProtKB/TrEMBL ID(s)
46 with_wikigene With WikiGene ID(s)
47 ensembl_gene_id Gene stable ID(s) [e.g. ENSMUSG00000000001]
48 ensembl_gene_id_version Gene stable ID(s) with version [e.g. ENSMUSG00000000001.5]
49 ensembl_transcript_id Transcript stable ID(s) [e.g. ENSMUST00000000001]
50 ensembl_transcript_id_version Transcript stable ID(s) with version [e.g. ENSMUST00000000001.5]
## Collecting annotations

# I also recover the attribute "ensembl_gene_id" to be able to merge the dataframes later
getBM(attributes = c("ensembl_gene_id", "external_gene_name", "chromosome_name", 
                     "start_position", "end_position", "strand"),
      filter = "ensembl_gene_id",
      values = row.names(gene_stat_norm),
      mart = ensembl) -> annotations_df


## Merging the dataframes

# adding the column "ensembl_gene_id" to the gene_stat_norm dataframe for easier merging
gene_stat_norm$ensembl_gene_id <- row.names(gene_stat_norm)

# Merge with dplyr, although I use right_join not left_join
gene_stat_norm <- right_join(annotations_df, gene_stat_norm, 
                            keep = FALSE)

# Number of genes for which no annotation could be recovered from Ensembl
length(gene_stat_norm[is.na(gene_stat_norm)])
[1] 2745

Challenge falcultatif:

Réordonnez les gènes par position génomique et affichez les lignes 5 premières et 5 dernières lignes de ce tableau de statistiques.

#### Sorting genes by chromosome ####
message("Sorting genes by chromosome")

kable(gene_stat_norm[50:60, 1:6], caption = "Dataframe avant tri")
Dataframe avant tri
ensembl_gene_id external_gene_name chromosome_name start_position end_position strand
50 ENSMUSG00000000339 Rtca 3 116282612 116301857 -1
51 ENSMUSG00000000340 Dbt 3 116306719 116343630 1
52 ENSMUSG00000000346 Dazap2 15 100513230 100518642 1
53 ENSMUSG00000000355 Mcts1 X 37689455 37702303 1
54 ENSMUSG00000000359 Rem1 2 152468871 152477118 1
55 ENSMUSG00000000374 Trappc10 10 78022559 78080475 -1
56 ENSMUSG00000000378 Ccm2 11 6496887 6546744 1
57 ENSMUSG00000000384 Tbrg4 11 6565598 6576067 -1
58 ENSMUSG00000000385 Tmprss2 16 97365882 97412395 -1
59 ENSMUSG00000000386 Mx1 16 97248235 97264107 -1
60 ENSMUSG00000000392 Fap 2 62331287 62404419 -1
gene_stat_norm <- arrange(gene_stat_norm, chromosome_name, start_position)
kable(gene_stat_norm[50:60, 1:6], caption = "Dataframe après tri par position génomique.")
Dataframe après tri par position génomique.
ensembl_gene_id external_gene_name chromosome_name start_position end_position strand
50 ENSMUSG00000056763 Cspp1 1 10108212 10206993 1
51 ENSMUSG00000102356 1700047N06Rik 1 10127250 10128169 -1
52 ENSMUSG00000067851 Arfgef1 1 10207796 10302895 -1
53 ENSMUSG00000102556 Gm37569 1 10267306 10269027 -1
54 ENSMUSG00000042501 Cpa6 1 10394945 10790170 -1
55 ENSMUSG00000103810 Gm38005 1 10519470 10522214 -1
56 ENSMUSG00000083422 Gm15604 1 10624323 10625778 -1
57 ENSMUSG00000103448 Gm37133 1 10852681 10854602 -1
58 ENSMUSG00000048960 Prex2 1 11063689 11373905 1
59 ENSMUSG00000103494 Gm37410 1 11434150 11437188 -1
60 ENSMUSG00000057715 A830018L16Rik 1 11484329 12046125 1
head(gene_stat_norm, 5)
     ensembl_gene_id external_gene_name chromosome_name start_position end_position strand     mean       var        sd       iqr      min      med      max   coef_var
1 ENSMUSG00000051951               Xkr4               1        3276124      3741721     -1 4.816674 1.3797317 1.1746198 2.0426500 3.050184 4.595623 6.681809 0.24386532
2 ENSMUSG00000103377            Gm37180               1        3435954      3438772     -1 2.314986 3.8640118 1.9657090 3.6903976 0.000000 1.867157 5.763218 0.84912350
3 ENSMUSG00000103161            Gm38148               1        3663115      3666126     -1 7.001132 0.2888639 0.5374606 0.3525436 6.205841 6.968086 8.836822 0.07676767
4 ENSMUSG00000102331            Gm19938               1        3717532      3729127     -1 4.758114 0.5164249 0.7186271 0.4096475 3.148896 4.705953 6.622021 0.15103193
5 ENSMUSG00000102592            Gm38385               1        3822233      3824583      1 5.857219 0.6559678 0.8099184 0.6477559 4.936802 5.660493 8.419961 0.13827695
tail(gene_stat_norm, 5)
         ensembl_gene_id external_gene_name chromosome_name start_position end_position strand     mean       var        sd       iqr      min      med      max   coef_var
20387 ENSMUSG00000108658               <NA>            <NA>             NA           NA     NA 4.938319 0.7136573 0.8447824 1.1299648 3.535143 4.911031 6.574707 0.17106681
20388 ENSMUSG00000108996               <NA>            <NA>             NA           NA     NA 3.364697 6.8214716 2.6117947 4.3200144 0.000000 3.717486 9.191275 0.77623478
20389 ENSMUSG00000109075               <NA>            <NA>             NA           NA     NA 1.239756 5.8909843 2.4271350 0.0000000 0.000000 0.000000 7.081730 1.95775172
20390 ENSMUSG00000109483               <NA>            <NA>             NA           NA     NA 3.707707 6.6925882 2.5870037 4.4430806 0.000000 4.943871 7.515378 0.69773683
20391 ENSMUSG00000109554               <NA>            <NA>             NA           NA     NA 6.866741 0.1589498 0.3986851 0.3635101 6.085779 6.869435 7.722308 0.05806032

Distribution des données

  • Dessinez sous forme d’un histogramme la distribution des valeurs après normalisation (tous échantillons confondus)
par(mfrow=c(1,1))
hist(fa_expr_log2,
     breaks = 100,
     cex.axis = 0.7,
     las = 1,
     col = "skyblue",
     xlab = "normalised counts",
     main = "Distribution of all normalised counts")
Distribution of all normalised counts

Distribution of all normalised counts

  • Dessinez un box plot par échantillon avant (sans et après normalisation de la distribution en log2) et après standardisation (normalisation inter-échantillon). Commentez la façon dont l’effet de la (des) normalisation(s) apparaît sur ces graphiques.
#### Box plots to show normalisation impact ####

# Raw data
boxplot(fa_expr_raw,
        main = "Raw expression",
        horizontal = TRUE,
        col = fa_meta$color,
        cex = 0.5,
        cex.axis = 0.8,
        las = 1)
Boxplots observation of the normalisation procedures.

Boxplots observation of the normalisation procedures.

# With filtered expression and log2 transformation
boxplot(log2(fa_expr_filtered + 1),
        main = "Filtered and log2 transformed expression",
        horizontal = TRUE,
        col = fa_meta$color,
        cex = 0.5,
        cex.axis = 0.8,
        las = 1)
Boxplots observation of the normalisation procedures.

Boxplots observation of the normalisation procedures.

# With filtered, standardised across samples and log2 transformation
boxplot(fa_expr_log2,
        main = "Standardised and log2 transformed expression",
        horizontal = TRUE,
        col = fa_meta$color,
        cex = 0.5,
        cex.axis = 0.8,
        las = 1)
Boxplots observation of the normalisation procedures.

Boxplots observation of the normalisation procedures.

Avec les données d’expression brutes, les valeurs sont étalées avec quelques outliers extrêmement exprimés et une grande majorité de gènes pas ou peu exprimés.

Le filtrage de ces gènes peu ou pas exprimés et la transformation en log2 permet de “regrouper” les valeurs d’expression et de réduire l’impact des valeurs extrêmes dans l’analyse. C’est cependant une normalisation seulement intra-échantillon.

La standardisation sur le troisième quartile permet une normalisation inter-échantillons. Les médianes de tous les échantillons sont presque alignées. Cela permet notamment de considérablement réduire l’impact de la taille des librairies pour l’analyse entre échantillons. Par exemple le day2_2 dont l’expression moyenne semble nettement plus faible, retrouve des valeurs similaire aux autres échantillons.

4. Analyse de regroupement des données

A vous de jouer!

Sélection de gènes d’expression élevée et variable

Pour réduire le nombre de gènes, nous allons écarter les gènes faiblement exprimés (log2 moyen inférieur à 4), et ne retenir que ceux qui montrent des variations importantes entre échantillons. Pour ce dernier critère, nous nous basons sur la variance.

Sélectionnez les gènes ayant un niveau log2 moyen minimal supérieur à 5 (\(m > 5\)) et une variance supérieure à 2 (\(s^2 > 2\)). Note: ces valeurs sont parfaitement arbitraires, elles ont été choisies pour obtenir un nombre raisonnable de gènes.

#### Selection of a subset of genes with igh expression and variance ####
message("Selecting genes with high expression and variance")

# Selecting high expression genes and with a high variance
most_expressed_genes_stat <- gene_stat_norm[(gene_stat_norm$mean > 5) & (gene_stat_norm$var > 2),]

# Recovering the desired genes from the normalised data
fa_expr_high_genes <- fa_expr_log2[most_expressed_genes_stat$ensembl_gene_id,]

Dessinez des histogrammes des valeurs d’expression avant et après cette sélection de gènes, et commentez les différences.

#### Histograms of expression before and after gene selection ####

par(mfrow=c(1, 2))
hist(fa_expr_log2,
     breaks = 100,
     xlim = c(0, 25),
     cex.main = 0.85,
     cex.axis = 0.7,
     las = 1,
     col = "skyblue",
     xlab = "normalised counts",
     main = "Distribution of all genes 
normalised counts")

hist(fa_expr_high_genes,
     breaks = 100,
     xlim = c(0, 25),
     cex.main = 0.85,
     cex.axis = 0.7,
     las = 1,
     col = "gold",
     xlab = "normalised counts",
     main = "Distribution of highly expressed genes 
normalised counts")
Distribution of normalised expression of all genes versus selected highly expressed genes.

Distribution of normalised expression of all genes versus selected highly expressed genes.

On a beaucoup réduit le nombres de valeurs d’expression. Il reste cependant des valeurs nulles en proportion assez élevée. En selectionnant sur la base d’une variance forte, on a sans doute gardé des gènes qui ne sont pas du tout exprimés dans certaines conditions, et activés dans d’autres, probablement en réponse au traitement à l’acide folique.

De plus le sommet du pic de distribution sur les gènes sélectionnés est situé à une valeur de comptage normalisée similaire à celle pour tous les gènes, malgré le choix de gènes en moyenne les plus exprimés. Peut-être parce qu’une bonne partie des gènes très exprimés constants on été retirés par le filtre sur la variance, on garde ainsi des gènes avec des valeurs d’expression faibles dans certaines conditions qui viennent “compenser” les valeurs fortes dans d’autres.

Dessinez un box plot par échantillon des valeurs d’expression avant et après sélection des gènes, et commentez le résultat.

#### Boxplots of expression before and after gene selection ####

par(mfrow=c(1, 1))
# With all genes
boxplot(fa_expr_log2,
        main = "Standardised and log2 transformed expression
all genes",
        horizontal = TRUE,
        col = fa_meta$color,
        cex = 0.5,
        cex.axis = 0.6,
        las = 1)
Boxplots of all genes versus selected genes normalised expression.

Boxplots of all genes versus selected genes normalised expression.

# With selected genes
boxplot(fa_expr_high_genes,
        main = "Standardised and log2 transformed expression
selected genes",
        horizontal = TRUE,
        col = fa_meta$color,
        cex = 0.5,
        cex.axis = 0.6,
        las = 1)
Boxplots of all genes versus selected genes normalised expression.

Boxplots of all genes versus selected genes normalised expression.

Les différences de statistiques inter-échantillons sont à nouveaux plus élevées, les médianes sont plus écartées. L’échantillon day2_2 est encore assez différent des autres, avec des valeurs d’expression plus étalées.

ACP

Dessinez un plot ACP des échantillons en les colorant par condition avant et après normalisation.

  • avec les comptages bruts de la matrice d’expression initiale (\(fa_expr\))
#### PCA from raw counts, all genes ####
message("Running PCA with raw counts, all genes")

res_pca_raw <- PCA(t(fa_expr_raw), scale.unit = TRUE, graph = FALSE)

fviz_pca_ind(res_pca_raw,
             col.ind = fa_meta$condition,
             mean.point = FALSE,
             title = "PCA on raw data, all genes")
PCA on raw data, all genes

PCA on raw data, all genes

  • avec la matrice de valeurs normalisées des gènes filtrés
#### PCA from normalised counts, all genes ####
message("Running PCA with normalised counts, all genes")

res_pca_log2 <- PCA(t(fa_expr_log2), scale.unit = TRUE, graph = FALSE)

fviz_pca_ind(res_pca_log2,
             col.ind = fa_meta$condition,
             mean.point = FALSE,
             title = "PCA on normalised data, all genes")
PCA on normalised data, all genes

PCA on normalised data, all genes

  • avec la matrice finale (transformation log2, filtre des gènes non-détectés, standardisation et sélection des gènes fortement exprimés et à haut coefficient de variation)
#### PCA from normalised counts, selected genes ####
message("Running PCA with normalised counts, selected genes")

res_pca_high_genes <- PCA(t(fa_expr_high_genes), scale.unit = TRUE, graph = FALSE)

fviz_pca_ind(res_pca_high_genes,
             col.ind = fa_meta$condition,
             mean.point = FALSE,
             title = "PCA on normalised data, selected genes")
PCA on normalised data, selected genes

PCA on normalised data, selected genes

La normalisation des données permet de rapprocher considérablement l’échantillon normal_2 et day1_1 qui semblaient être des outliers, de leurs réplicats biologiques. Elle permet également de rassembler les échantillons day2_1 et day2_3. La normalisation permet également de mieux séparer les conditions day3 et day7.

day2_2 semble être un outlier, quel que soit le traitement des données. Si on l’exclut, on observe dans l’ACP sur les gènes sélectionnés une claire séparation le long de la dimension 2 entre les échantillons après traitement à l’acide folique et les échantillons normaux. On observe également que les conditions day14 “redescendent” le long de cette dimension, probablement car l’effet de l’injection se résorbe. On peut même observer une disposition cyclique horaire partant de normal, passant aux temps récents après l’injection puis tendant à retourner vers les conditions normales.

Clustering

  • Calculez les matrices de distance entre échantillons, en utilisant respectivement les distances euclidienne (dist()), coefficient de Pearson (cor(, method = "pearson")) et de Spearman (cor(, method = "spearman")).
#### Sample distances ####
message("Computing inter-sample distances")

## Euclidian
# On transpose la matrice d'expression pour calculer la distance entre échantillons.
dist_euclidian <- dist(t(fa_expr_high_genes), method = "euclidean")


# Les distances de Pearson et Spearman sont calculées en soustrayant à 1 la corrélation de Pearson ou Spearman

## Pearson
dist_pearson <- as.dist(1 - cor(fa_expr_high_genes, use = "everything", 
                                method = "pearson"))

## Spearman
dist_spearman <- as.dist(1 - cor(fa_expr_high_genes, use = "everything", 
                                 method = "spearman"))
  • Effectuez un clustering hiérarchique des échantillons, en utilisant le critère complete pour l’agglomération. Comparez les arbres d’échantillons obtenus avec ces trois métriques et choisissez celle qui vous paraît la plus pertinente.
#### Sample clustering ####
message("Sample clustering")

tree_euclidian <- hclust(dist_euclidian, method = "complete")
tree_pearson <- hclust(dist_pearson, method = "complete")
tree_spearman <- hclust(dist_spearman, method = "complete")

par(bg = "darkgrey", mfrow=c(1, 1))
plotColoredClusters(tree_euclidian, labs = fa_meta$sampleName,
                    ylab = NA, xlab = NA, cex = 1, las = 1,
                    cols = fa_meta$color, col = "white",
                    main = "Samples Euclidian distance hierarchical clustering,
complete linkage")
Dendograms samples clustering.

Dendograms samples clustering.

plotColoredClusters(tree_pearson, labs = fa_meta$sampleName,
                    ylab = NA, xlab = NA, cex = 1, las = 1,
                    cols = fa_meta$color, col = "white",
                    main = "Samples Pearson distance hierarchical clustering,
complete linkage")
Dendograms samples clustering.

Dendograms samples clustering.

plotColoredClusters(tree_spearman, labs = fa_meta$sampleName,
                    ylab = NA, xlab = NA, cex = 1, las = 1,
                    cols = fa_meta$color, col = "white",
                    main = "Samples Spearman distance hierarchical clustering,
complete linkage")
Dendograms samples clustering.

Dendograms samples clustering.

Le clustering basé sur la distance euclidienne sépare complètement l’échantillon day2_2 des autres, il est traité comme un outlier très marqué. Les échantillons normaux sont groupés entre eux et séparé d’un cluster qui englobe tous les échantillons traités à part le day2_2. Ce cluster d’échantillons traités est séparé en un groupe de prélèvements peu après le traitement à l’acide folique (day1, day2, day3) et un autre avec des temps plus longs (day7 et day14).

Les clusterings basés sur les distances de Spearman et Pearson donnent des résultats très proches. Deux super clusters dont un comprenant les échantillons prélevés peu après le traitement, à l’intérieur duquel les différentes temps sont bien regroupés (mis à part le day2_2), et un regroupant les conditions sans traitement et les prélèvement plus tardifs.

Les trois clusterings donnent des résultats qui me semblent cohérents, cependant l’excusion nette du day2_2 des autres conditions par la distance euclidienne me parait justifiée (au regard de la PCA) ainsi que la séparation plus franche des conditions normales et des conditions traitées.

Je décide d’utiliser le clustering hierarchique des échantillons basé sur la distance euclidienne.

  • Effectuez un clustering hiérarchique des gènes en utilisant la distance basée sur le coefficient de Pearson et la règle d’agglomération complète
#### Gene tree with Pearson correlation ####
message("Drawing gene tree")

# Pearson distance gene
dist_gene_pearson <- as.dist(1 - cor(t(fa_expr_high_genes), 
                                use = "everything", method = "pearson"))
# tree
tree_gene_pearson <- hclust(dist_gene_pearson, method = "complete")
  • Dessinez un arbre avec le résultat du clustering des gènes et commentez sa structure. Si vous deviez choisir de façon arbitraire un nombre de clusters, que choisiriez-vous ? Pourquoi ? Pas de panique, nous pouvons assumer ici que la réponse comporte une part de subjectivité.
#### Plot the gene tree with boxes to denote the clusters ####
message("Plotting gene tree")

par(bg = "white")
plot(tree_gene_pearson, las = 1, 
     ylab = NA, xlab = NA, cex = 0.1, hang = -1, col = "grey",
     main = "Genes Pearson distance hierarchical clustering,
complete linkage")

# Estimating an appropriate number of clusters
abline(h=1.7, col= "red")

# Let's try cutting the tree into 6 clusters at the height 1.7

clusters_gene_pearson <- cutree(tree_gene_pearson, h=1.7)

# How many genes are in each cluster?
table(clusters_gene_pearson)
clusters_gene_pearson
  1   2   3   4   5 
321 122 102 111  34 
# Saving clustering results in gene dataframe 
most_expressed_genes_stat$cluster <- clusters_gene_pearson


# We add the cluster information in the most_expressed_genes_stat
my_colors = c("orange", "blue", "darkred", 
              "skyblue", "darkgreen")

plot(tree_gene_pearson, las = 1, 
     ylab = NA, xlab = NA, cex = 0.1, hang = -1, col = "grey",
     main = "Genes Pearson distance hierarchical clustering,
complete linkage")

  rect.hclust(tree_gene_pearson, h=1.7, border = my_colors)

L’arbre comporte une séparation en deux cluters principaux qui se subdivisent ensuite rapidement. Le choix du nombre de clusters n’est pas évident, une séparation en 5 ou 6 clusters me paraitrait raisonnable. En coupant l’arbre à une hauteur de 1.7, j’opte pour 5 clusters.

  • Dessinez une heatmap du résultat, en sélectionnant les deux résultats de clustering ci-dessus pour les gènes et les échantillons.
#### Heatmap with biclustering ####
message("heatmap with biclustering")

annot_clust = data.frame(as.factor(most_expressed_genes_stat$cluster))
colnames(annot_clust) <- "cluster"
rownames(annot_clust) <- rownames(fa_expr_high_genes)

pheatmap(fa_expr_high_genes,
         cluster_rows = TRUE,
         cluster_cols = TRUE,
         clustering_distance_rows = dist_gene_pearson,
         clustering_distance_cols = dist_euclidian,
         border_color = NA,
         show_rownames = FALSE,
         annotation_row = annot_clust,
         annotation_names_row = FALSE,
         cutree_rows = 5,
         cutree_cols = 5,
         scale = "row",
         use_raster = TRUE,
         angle_col = "45",
         main = "Biclustering of genes (Pearson distance) 
and samples (euclidian distance)")
Biclustering of genes and samples.

Biclustering of genes and samples.

  • Dessinez une heatmap du résultat, en affichant un arbre sur les gènes mais pas sur les échantillons
#### Heatmap with clustering on genes only ####
message("Heatmap with gene clustering")

pheatmap(fa_expr_high_genes,
         cluster_rows = TRUE,
         cluster_cols = FALSE,
         clustering_distance_rows = dist_gene_pearson,
         border_color = NA,
         show_rownames = FALSE,
         legend = FALSE,
         annotation_row = annot_clust,
         annotation_names_row = FALSE,
         cutree_rows = 5,
         scale = "row",
         use_raster = TRUE,
         angle_col = "45",
         main = "Heatmap with clustering of genes only 
(Pearson distance)")
Heatmap with clustering of genes (Pearson distance)

Heatmap with clustering of genes (Pearson distance)

Interprétez les résultats en quelques phrases.

On observe avec ou sans clustering des échantillons le profil atypique de l’échantillon day2_2. À mon sens, cela confirme le choix de la distance euclidienne pour le clustering des échantillons. Cet échantillon surexprime nettement le cluster de gènes 3 et sous-exprime le cluster 1.

Ensuite, on observe que le cluster de gènes 2 est sous-exprimé dans les condions normales par rapport aux conditions traitées. Ces gènes sont possiblement impliqués dans la réponse au traitement à l’acide folique et à l’atteinte aux reins qu’il cause. Le niveau d’expression du cluster 2 redescend dans les day14.

Les clusters de gènes 1 et 4 sont globalement moins exprimés dans les condions peu après le traitement. Leurs niveaux d’expression remontent dans les conditions plus tardives.

5. Enrichissement fonctionnel

A vous de jouer!

Effectuez une analyse d’enrichissement fonctionnel avec les principaux clusters obtenus dans la section précédente.

#### Run enrichment analysis with gost() ####
message("Running enrichment analysis for the selected genes")

gene_list <- most_expressed_genes_stat$ensembl_gene_id
gost_res <- gost(query = gene_list,
                 organism = "mmusculus",
                 significant = TRUE,
                 user_threshold = 0.05, # custom p-value threshold for significance
                 correction_method = "fdr",
                 domain_scope = "annotated",
                 as_short_link = FALSE)

gostplot(gost_res, capped = TRUE, 
         interactive = TRUE)

Fonctional enrichment analysis

Je comprends mal la différence entre les énoncés de la question 5 et du challenge facultatif. Je suppose que dans la question obligatoire il est demandé une analyse d’enrichissement globale sur tous les gènes les plus exprimés et variables et que les clusters de gènes n’interviennent que dans la section facultative…

Challenge falcultatif:

Effectuez une analyse d’enrichissement sur chacun des clusters obtenus à partir de l’arbre des gènes.

#### Enrichment by cluster ####
message("Enrichment analysis by clusters")

## Extracting list of genes by cluster

cluster1 <- most_expressed_genes_stat[most_expressed_genes_stat$cluster == 1, "ensembl_gene_id"]
cluster2 <- most_expressed_genes_stat[most_expressed_genes_stat$cluster == 2, "ensembl_gene_id"]
cluster3 <- most_expressed_genes_stat[most_expressed_genes_stat$cluster == 3, "ensembl_gene_id"]
cluster4 <- most_expressed_genes_stat[most_expressed_genes_stat$cluster == 4, "ensembl_gene_id"]
cluster5 <- most_expressed_genes_stat[most_expressed_genes_stat$cluster == 5, "ensembl_gene_id"]

list_clusters_genes = list(cluster1, cluster2, cluster3,
                           cluster4, cluster5)

gost_clusters_res <- gost(query = list_clusters_genes,
                          organism = "mmusculus",
                          significant = TRUE, 
                          multi_query = TRUE,
                          user_threshold = 0.05, # custom p-value threshold for significance
                          correction_method = "fdr",
                          domain_scope = "annotated",
                          as_short_link = FALSE)

gostplot(gost_clusters_res, capped = TRUE, 
         interactive = TRUE)

Fonctional enrichment analysis, by cluster

Quelques observations :

-Les gènes du cluster 2, exprimés dans les conditions peu après le traitment à l’acide folique, sont souvent impliqués dans la réponse immunitaire et l’inflamation, probablement pour répondre au choc produit par le traitement sur les reins de la souris.

-Le cluster 4 (dont l’expression baisse dans les conditions peu après le traitement avant de remonter) comprend des gènes impliqués dans le transport transmembranaire, la glycolyse… Il reflète probablement l’activité normale des reins.

-Les gènes du cluster 3 (activé dans l’échantillons outlier day2_2) semblent impliqués dans la division cellulaire…

Conclusions générales

Résumez en quelques phrases vos conclusions à partir des résultats obtenus.

Dans l’analyse d’enrichissement générale on semble retrouver de nombreux gènes impliqués dans la réponse immunitaire.

L’analyse par cluster tend à suggérer que ces gènes sont notamment activés en réponse au traitement à l’acide folique dans les jours 1, 2, 3 et 7. Leur expression rediminue ensuite au jour 14.

Les gènes reflétant l’activité normale du rein sont au contraire moins exprimés juste après l’injection d’acide folique (jours 1 et 2) puis leur expression commence à remonter après le jour 3.

En conclusion, on observe ici la certaine reversibilité du traitement à l’acide folique sur les souris. Les reins retrouvent une expression génétique proche de la normale. Cependant, si l’on regarde les heatmaps, on observe une sous-partie du cluster de gènes 1 dont l’expression est plus haute que la normale aux jours 7 et 14, peut-être que ceux ci refletent des séquelles plus durables du traitement sur le rein ?

Session info

#### Session info ####
sessionInfo()
R version 4.0.3 (2020-10-10)
Platform: x86_64-conda-linux-gnu (64-bit)
Running under: CentOS Linux 7 (Core)

Matrix products: default
BLAS/LAPACK: /shared/ifbstor1/software/miniconda/envs/r-4.0.3/lib/libopenblasp-r0.3.10.so

locale:
 [1] LC_CTYPE=en_US.UTF-8       LC_NUMERIC=C               LC_TIME=en_US.UTF-8        LC_COLLATE=en_US.UTF-8     LC_MONETARY=en_US.UTF-8    LC_MESSAGES=en_US.UTF-8    LC_PAPER=en_US.UTF-8       LC_NAME=C                  LC_ADDRESS=C               LC_TELEPHONE=C            
[11] LC_MEASUREMENT=en_US.UTF-8 LC_IDENTIFICATION=C       

attached base packages:
[1] stats     graphics  grDevices utils     datasets  methods   base     

other attached packages:
 [1] biomaRt_2.46.3        ClassDiscovery_3.3.13 oompaBase_3.2.9       cluster_2.1.1         dplyr_1.0.6           pheatmap_1.0.12       gprofiler2_0.2.0      factoextra_1.0.7      ggplot2_3.3.3         FactoMineR_2.4        knitr_1.33           

loaded via a namespace (and not attached):
  [1] colorspace_2.0-1     ggsignif_0.6.1       ellipsis_0.3.2       rio_0.5.26           mclust_5.4.7         ggpubr_0.4.0         farver_2.1.0         ggrepel_0.9.1        DT_0.17              bit64_4.0.5          AnnotationDbi_1.52.0 fansi_0.4.2          xml2_1.3.2           leaps_3.1           
 [15] cachem_1.0.5         jsonlite_1.7.2       broom_0.7.5          dbplyr_2.1.1         shiny_1.6.0          compiler_4.0.3       httr_1.4.2           backports_1.2.1      assertthat_0.2.1     fastmap_1.1.0        lazyeval_0.2.2       later_1.2.0          htmltools_0.5.1.1    prettyunits_1.1.1   
 [29] tools_4.0.3          gtable_0.3.0         glue_1.4.2           rappdirs_0.3.3       Rcpp_1.0.6           carData_3.0-4        Biobase_2.50.0       cellranger_1.1.0     jquerylib_0.1.4      vctrs_0.3.8          crosstalk_1.1.1      xfun_0.23            stringr_1.4.0        openxlsx_4.2.3      
 [43] mime_0.10            lifecycle_1.0.0      rstatix_0.7.0        XML_3.99-0.6         MASS_7.3-53.1        scales_1.1.1         hms_1.1.0            promises_1.2.0.1     parallel_4.0.3       RColorBrewer_1.1-2   yaml_2.2.1           curl_4.3.1           memoise_2.0.0        sass_0.4.0          
 [57] stringi_1.6.2        RSQLite_2.2.7        highr_0.9            S4Vectors_0.28.1     BiocGenerics_0.36.1  zip_2.1.1            rlang_0.4.11         pkgconfig_2.0.3      bitops_1.0-7         evaluate_0.14        lattice_0.20-41      purrr_0.3.4          htmlwidgets_1.5.3    labeling_0.4.2      
 [71] bit_4.0.4            tidyselect_1.1.1     magrittr_2.0.1       R6_2.5.0             IRanges_2.24.1       generics_0.1.0       oompaData_3.1.1      DBI_1.1.1            pillar_1.6.1         haven_2.3.1          foreign_0.8-81       withr_2.4.2          scatterplot3d_0.3-41 abind_1.4-5         
 [85] RCurl_1.98-1.3       tibble_3.1.2         crayon_1.4.1         car_3.0-10           utf8_1.2.1           BiocFileCache_1.14.0 plotly_4.9.3         rmarkdown_2.8        progress_1.2.2       grid_4.0.3           readxl_1.3.1         data.table_1.14.0    blob_1.2.1           forcats_0.5.1       
 [99] digest_0.6.27        flashClust_1.01-2    xtable_1.8-4         tidyr_1.1.3          httpuv_1.6.1         openssl_1.4.4        stats4_4.0.3         munsell_0.5.0        viridisLite_0.4.0    bslib_0.2.5.1        askpass_1.1         
LS0tCnRpdGxlOiAiTWluaS1wcm9qZXQgMjAyMSAtIEV4cGxvcmF0aW9uIGRlcyBkb25uw6llcyBkZSBQYXZrb3ZpYyIKYXV0aG9yOiAiRG9taXRpbGxlIEphcnJpZ2UiCmRhdGU6ICdgciBTeXMuRGF0ZSgpYCcKb3V0cHV0OgogIGh0bWxfZG9jdW1lbnQ6CiAgICBzZWxmX2NvbnRhaW5lZDogeWVzCiAgICBjb2RlX2Rvd25sb2FkOiB0cnVlCiAgICBmaWdfY2FwdGlvbjogeWVzCiAgICBoaWdobGlnaHQ6IHplbmJ1cm4KICAgIHRoZW1lOiBjZXJ1bGVhbgogICAgdG9jOiB5ZXMKICAgIHRvY19kZXB0aDogMwogICAgdG9jX2Zsb2F0OiB5ZXMKICAgIGNvZGVfZm9sZGluZzogImhpZGUiCiAgcGRmX2RvY3VtZW50OgogICAgZmlnX2NhcHRpb246IHllcwogICAgaGlnaGxpZ2h0OiB6ZW5idXJuCiAgICB0b2M6IHllcwogICAgdG9jX2RlcHRoOiAzCmVkaXRvcl9vcHRpb25zOiAKICBjaHVua19vdXRwdXRfdHlwZTogY29uc29sZQotLS0KCgpgYGB7ciBzZXR0aW5ncywgaW5jbHVkZT1GQUxTRSwgZWNobz1GQUxTRSwgZXZhbD1UUlVFfQpvcHRpb25zKHdpZHRoID0gMzAwKQojIG9wdGlvbnMoZW5jb2RpbmcgPSAnVVRGLTgnKQprbml0cjo6b3B0c19jaHVuayRzZXQoCiAgZmlnLndpZHRoID0gNywgZmlnLmhlaWdodCA9IDUsIAogIGZpZy5wYXRoID0gJ2ZpZ3VyZXMvbWluaS1wcm9qZXRfJywKICBmaWcuYWxpZ24gPSAiY2VudGVyIiwgCiAgc2l6ZSA9ICJ0aW55IiwgCiAgZWNobyA9IFRSVUUsIAogIGV2YWwgPSBUUlVFLCAKICB3YXJuaW5nID0gRkFMU0UsIAogIG1lc3NhZ2UgPSBGQUxTRSwgCiAgcmVzdWx0cyA9IFRSVUUsIAogIGNvbW1lbnQgPSAiIikKCm9wdGlvbnMoc2NpcGVuID0gMTIpICMjIE1heCBudW1iZXIgb2YgZGlnaXRzIGZvciBub24tc2NpZW50aWZpYyBub3RhdGlvbgojIGtuaXRyOjphc2lzX291dHB1dCgiXFxmb290bm90ZXNpemUiKQpgYGAKCmBgYHtyIHNldF93b3JraW5nX2RpcmVjdG9yeX0Kc2V0d2QoIi9zaGFyZWQvaWZic3RvcjEvcHJvamVjdHMvZHViaWkyMDIxL2RqYXJyaWdlL20zLXN0YXQtUi9taW5pLXByb2pldCIpCmBgYAoKYGBge3IgbGlicmFyaWVzLCBlY2hvPUZBTFNFLCBldmFsPVRSVUV9CiMjIyMgUmVxdWlyZWQgbGlicmFyaWVzICMjIyMKCiMgTG9hZCByZXF1aXJlZCBDUkFOIFIgbGlicmFyaWVzCnJlcXVpcmVkX2NyYW5MaWIgPC0gYygia25pdHIiLCAKICAgICAgICAgICAgICAgICAgICAgICJGYWN0b01pbmVSIiwgCiAgICAgICAgICAgICAgICAgICAgICAiZmFjdG9leHRyYSIsIAogICAgICAgICAgICAgICAgICAgICAgImdwcm9maWxlcjIiLAogICAgICAgICAgICAgICAgICAgICAgInBoZWF0bWFwIiwKICAgICAgICAgICAgICAgICAgICAgICJkcGx5ciIsCiAgICAgICAgICAgICAgICAgICAgICAiQ2xhc3NEaXNjb3ZlcnkiKQoKZm9yIChsaWIgaW4gcmVxdWlyZWRfY3JhbkxpYikgewogIGlmICghcmVxdWlyZShsaWIsIGNoYXJhY3Rlci5vbmx5ID0gVFJVRSkpIHsKICAgIGluc3RhbGwucGFja2FnZXMobGliKQogIH0KICByZXF1aXJlKGxpYiwgY2hhcmFjdGVyLm9ubHkgPSBUUlVFKQp9CgoKIyBMb2FkIEJpb2NvbmR1Y3RvciBsaWJyYXJpZXMgKG9ubHkgYmlvbWFSdCBzbyBmYXIhKQoKcmVxdWlyZWRfQmlvY29uZHVjdG9yIDwtIGMoImJpb21hUnQiKQoKZm9yIChsaWIgaW4gcmVxdWlyZWRfQmlvY29uZHVjdG9yKSB7CiAgaWYgKCFyZXF1aXJlKGxpYiwgY2hhcmFjdGVyLm9ubHkgPSBUUlVFKSl7CiAgICBCaW9jTWFuYWdlcjo6aW5zdGFsbChsaWIpCiAgfQogIHJlcXVpcmUobGliLCBjaGFyYWN0ZXIub25seSA9IFRSVUUpCn0KICAgIAoKCmthYmxlKGFzLmRhdGEuZnJhbWUoYyhyZXF1aXJlZF9jcmFuTGliLCByZXF1aXJlZF9CaW9jb25kdWN0b3IpKSwKICAgICAgY29sLm5hbWVzID0gImxpYnJhcmllcyIsCiAgICAgIGNhcHRpb24gPSAiTG9hZGVkIHJlcXVpcmVkIGxpYnJhcmllcyIKICAgICkKCmBgYAoKIyMgU3lub3BzaXMgZHUgcHJvamV0CgojIyMgVHJhdmFpbCBkZW1hbmTDqQoKTGUgYnV0IGRlIGNlIHRyYXZhaWwgZXN0IGRlIG1ldHRyZSBlbiBvZXV2cmUgbGVzIG3DqXRob2RlcyB2dWVzIGRhbnMgbGUgbW9kdWxlIDMgIlIgZXQgc3RhdGlzdGlxdWVzIiBwb3VyIGV4cGxvcmVyIGxlIGpldSBkZSBkb25uw6llcyBkZSBQYXZrb3ZpYywgZXQgZGUgcmVuZHJlIHVuIHJhcHBvcnQgZCdhbmFseXNlIGF1IGZvcm1hdCBgLlJtZGAuIAoKTm91cyBmb3Vybmlzc29ucyBjaS1kZXNzb3VzIHVuZSB0cmFtZSBhdmVjIGxlcyBwcmluY2lwYWxlcyBzZWN0aW9ucyBhdHRlbmR1ZXMuIENlcnRhaW5lcyBjb250aWVubmVudCBkw6lqw6AgZHUgY29kZS4gVm91cyBkZXZyZXogZW4gY29tcGzDqXRlciBkJ2F1dHJlcy4gU2VudGV6LXZvdXMgbGlicmVzIGQnYWRhcHRlciBjZXR0ZSB0cmFtZSBvdSBkJ3kgYWpvdXRlciBkZXMgYW5hbHlzZXMgY29tcGzDqW1lbnRhaXJlcyBzaSBlbGxlcyB2b3VzIGFpZGVudCDDoCBpbnRlcnByw6l0ZXIgdm9zIHLDqXN1bHRhdHMuIAoKIyMjIFJlbWlzZSBkdSByYXBwb3J0CgpEYXRlOiAqKmxlIDI2IG1haSAyMDIxIMOgIG1pbnVpdCoqLiAgU2kgdm91cyBhbnRpY2lwZXogdW4gcHJvYmzDqG1lIHBvdXIgcmVtZXR0cmUgbGUgcmFwcG9ydCDDoCBjZXR0ZSBkYXRlIGNvbnRhY3Rlei1ub3VzIGF1c3NpIHJhcGlkZW1lbnQgcXVlIHBvc3NpYmxlIHBvdXIgcXVlIG5vdXMgcHVpc3Npb25zIHByw6l2b2lyIHVuZSByZW1pc2UgcGx1cyB0YXJkaXZlLiAKCi0gQ29tbWVuY2V6IHBhciByZW5vbW1lciBsZSBmaWNoaWVyIC5SbWQgZW4gcmVtcGxhw6dhbnQgUHJlbm9tLU5PTSBwYXIgdm9zIG5vbSBldCBwcsOpbm9tLiAKLSBMZSByYXBwb3J0IGVzdCBhdHRlbmR1IGVuIGZvcm1hdHMgLlJtZCArIC5IVE1MIChlbiBnYXJkYW50IGwnb3B0aW9uIHNlbGZfY29udGFpbmVkIGRlIGwnZW4tdMOqdGUgYWN0aXbDqWUpLiAKLSBEw6lwb3NleiBsZXMgZmljaGllcnMgZGFucyB1biBzb3VzLWRvc3NpZXIgZGUgdm90ZSBjb21wdGUgZHUgY2x1c3Rlci4gQXR0ZW50aW9uLCB2ZWlsbGV6IMOgIHJlc3BlY3RlciBwcsOpY2lzw6ltZW50IGNldHRlIHN0cnVjdHVyZSBkZSBjaGVtaW4gY2FyIG5vdXMgbm91cyBiYXNlcm9ucyBkZXNzdXMgcG91ciByw6ljdXDDqXJlciB2b3MgcsOpc3VsdGF0cy4gCgogICAgYC9zaGFyZWQvcHJvamVjdHMvZHViaWkyMDIxL1tsb2dpbl0vbTMtc3RhdC1SL21pbmktcHJvamV0YCAKCiMjIyBDcml0w6hyZXMgZCfDqXZhbHVhdGlvbgoKLSBSZXByb2R1Y3RpYmlsaXTDqSBkZXMgYW5hbHlzZXM6IG5vdXMgdGVudGVyb25zIGRlIHJlZ8OpbsOpcmVyIGxlIHJhcHBvcnQgSFRNTCDDoCBwYXJ0aXIgZGUgdm90cmUgUm1kLCBlbiBwYXJ0YW50IGRlIG5vdHJlIGNvbXB0ZSBzdXIgbGUgc2VydmV1ciBJRkIuIAotIE1hbmlwdWxhdGlvbiBkZXMgb2JqZXRzIFIKLSBNb2JpbGlzYXRpb24gZGVzIG3DqXRob2RlcyBzdGF0aXN0aXF1ZXMgdnVlcyBhdSBjb3VycwotIFBlcnRpbmVuY2UgZGVzIGludGVycHLDqXRhdGlvbnMgc3RhdGlzdGlxdWVzCi0gUGVydGluZW5jZSBkZXMgaW50ZXJwcsOpdGF0aW9ucyBiaW9sb2dpcXVlcwotIENsYXJ0w6kgZGUgbGEgcsOpZGFjdGlvbgotIENsYXJ0w6kgZGVzIGlsbHVzdHJhdGlvbnMgKGZpZ3VyZXMgZXQgdGFibGVhdXgpOiBncmFwaGlzbWVzLCBsw6lnZW5kZXMgLi4uCgpOb3VzIHZvdXMgZW5jb3VyYWdlb25zIMOgIGFzc3VyZXIgbGEgbGlzaWJpbGl0w6kgZGUgdm90cmUgY29kZSAoc3ludGF4ZSwgbm9tbWFnZSBkZXMgdmFyaWFibGVzLCBjb21tZW50YWlyZXMgZGUgY29kZSkKCiMjIyBPYmplY3RpZnMgc2NpZW50aWZpcXVlcwoKTm91cyBwYXJ0b25zIGR1IG3Dqm1lIGpldSBkZSBkb25uw6llcyAqRmlsIFJvdWdlKiBkZSBjZSBtb2R1bGUgaXNzdWVzIGRlIGxhIHB1YmxpY2F0aW9uIFBhdmtvdmljLCBNLiwgUGFudGFubywgTC4sIEdlcmxhY2gsIEMuVi4gZXQgYWwuIE11bHRpIG9taWNzIGFuYWx5c2lzIG9mIGZpYnJvdGljIGtpZG5leXMgaW4gdHdvIG1vdXNlIG1vZGVscy4gU2NpIERhdGEgNiwgOTIgKDIwMTkpLiBodHRwczovL2RvaS5vcmcvMTAuMTAzOC9zNDE1OTctMDE5LTAwOTUtNQoKKipSYXBwZWwgc3VyIGxlcyDDqWNoYW50aWxsb25zOioqCgpEZXV4IG1vZMOobGVzIGRlIGZpYnJvc2UgcsOpbmFsZSBjaGV6IGxhIHNvdXJpcyBzb250IMOpdHVkacOpczoKCjEuIExlIHByZW1pZXIgZXN0IHVuIG1vZMOobGUgZGUgbsOpcGhyb3BhdGhpZSByw6l2ZXJzaWJsZSBpbmR1aXRlIHBhciBsJ2FjaWRlIGZvbGlxdWUgKGZvbGljIGFjaWQgKEZBKSkuIExlcyBzb3VyaXMgb250IMOpdMOpIHNhY3JpZmnDqWVzIGF2YW50IGxlIHRyYWl0ZW1lbnQgKG5vcm1hbCksIHB1aXMgw6Agam91ciAxLCAyLCA3IGV0IDE0IChkYXkxLC4uLikgYXByw6hzIHVuZSBzZXVsZSBpbmplY3Rpb24gZCdhY2lkZSBmb2xpcXVlLgoKMi4gTGUgc2Vjb25kIGVzdCB1biBtb2TDqGxlIGlycsOpdmVyc2libGUgaW5kdWl0IGNocmlydXJnaWNhbGVtZW50ICh1bmlsYXRlcmFsIHVyZXRlcmFsIG9ic3RydWN0aW9uIChVVU8pKS4gbGVzIHNvdXJpcyBvbnQgw6l0w6kgc2FjcmlmacOpZXMgYXZhbnQgb2JzdHJ1Y3Rpb24gKGRheSAwKSBldCDDoCAzLCA3IGV0IDE0IGpvdXJzIGFwcsOocyBvYnN0cnVjdGlvbiBwYXIgbGlnYXRpb24gZGUgbCd1cmV0w6hyZSBkdSByZWluIGdhdWNoZS4KCkEgcGFydGlyIGRlIGNlcyBleHRyYWl0cyBkZSByZWluLCBsJ0FSTiBtZXNzYWdlciB0b3RhbCBldCBsZXMgcGV0aXRzIEFSTnMgb250IMOpdMOpIHPDqXF1ZW5jw6lzIGV0IGxlcyBwcm90w6lpbmVzIGNhcmF0w6lyaXPDqWVzIHBhciBzcGVjdHJvbcOpdHJpZSBkZSBtYXNzZSBlbiB0YW5kZW0gKFRNVCkuCgoqKkJ1dCBzY2llbnRpZmlxdWU6KiogRGFucyBsZSB0dXRvcmllbCBzdXIgbGVzIGRhdGFmcmFtZXMsIHZvdXMgYXZleiB0cmF2YWlsbMOpIHN1ciBsZXMgZG9ubsOpZXMgZGUgKioqdHJhbnNjcmlwdG9tZSBkdSBtb2TDqGxlIFVVTyoqKi4gRGFucyBjZSBtaW5pLXByb2pldCwgdm91cyBhbGxleiB0cmF2YWlsbGVyIHN1ciBsZXMgZG9ubsOpZXMgZHUgKioqdHJhbnNjcmlwdG9tZSBkdSBtb2TDqGxlIEZBKioqIGFmaW4gZGUgcmVncm91cGVyIGxlcyBvYnNlcnZhdGlvbnMgKMOpY2hhbnRpbGxvbikgZXQgbGVzIGfDqG5lcyBzZWxvbiBkZXMgcHJvZmlscyBkJ2V4cHJlc3Npb24gc2ltaWxhaXJlcy4KCioqVm90cmUgcHJvamV0IHNlIGTDqWNvbXBvc2UgZW4gNSBwYXJ0aWVzIGRvbnQgMyBzZXJvbnQgw6AgcsOpYWxpc2VyIHBhciB2b3VzOioqCgoxLiBTdGF0aXRpcXVlcyBkZXNjcmlwdGl2ZXMgZGVzIGRvbm7DqWVzIGJydXRlczogY29tbWFuZGVzIGZvdXJuaWVzCjIuIE5vcm1hbGlzYXRpb24gZGVzIGRvbm7DqWVzIDogY29tbWFuZGVzIGZvdXJuaWVzCjMuIFN0YXRpc3RpcXVlcyBkZXNjcmlwdGl2ZXMgZGVzIGRvbm7DqWVzIG5vcm1hbGlzw6llczogw6Agdm91cyBkZSBqb3Vlcgo0LiBBbmFseXNlIGRlIHJlZ3JvdXBlbWVudCBkZXMgZG9ubsOpZXM6IMOgIHZvdXMgZGUgam91ZXIKNS4gQW5hbHlzZSBkJ2VucmljaGlzc2VtZW50IGZvbmN0aW9ubmVsOiDDoCB2b3VzIGRlIGpvdWVyCgojIyAxLiBMZXMgZG9ubsOpZXMgYnJ1dGVzCgoqKipWb3VzIG4nYXZleiByaWVuIMOgIGNvZGVyIGljaS4gTGUgY29kZSBlc3QgZm91cm5pLioqKgoKIyMjIENoYXJnZW1lbnQgZGVzIGRvbm7DqWVzIGJydXRlcwoKTGUgYmxvYyBzdWl2YW50IGNvbnRpZW50IHVuZSBmb25jdGlvbiBxdWkgcGVybWV0IGRlIHTDqWzDqWNoYXJnZXIgdW4gZmljaGllciBkYW5zIGwnZXNwYWNlIGRlIHRyYXZhaWwsIHNhdWYgcydpbCBlc3QgZMOpasOgIHByw6lzZW50LiBOb3VzIGwndXRpbGlzZXJvbnMgZW5zdWl0ZSBwb3VyIHTDqWzDqWNoYXJnZXIgbGVzIGRvbm7DqWVzIMOgIGFuYWx5c2VyIGVuIMOpdml0YW50IGRlIHJlZmFpcmUgbGUgdHJhbnNmZXJ0IMOgIGNoYXF1ZSBleMOpY3V0aW9uIGRlIGwnYW5hbHlzZS4gCgpgYGB7ciBmdW5jdGlvbl9kb3dubG9hZF9vbmx5X29uY2V9CiMnIEB0aXRsZSBEb3dubG9hZCBhIGZpbGUgb25seSBpZiBpdCBpcyBub3QgeWV0IGhlcmUKIycgQGF1dGhvciBKYWNxdWVzIHZhbiBIZWxkZW4gZW1haWx7SmFjcXVlcy52YW4tSGVsZGVuQEBmcmFuY2UtYmlvaW5mb3JtYXRpcXVlLmZyfQojJyBAcGFyYW0gdXJsX2Jhc2UgYmFzZSBvZiB0aGUgVVJMLCB0aGF0IHdpbGwgYmUgcHJlcGVuZGVkIHRvIHRoZSBmaWxlIG5hbWUKIycgQHBhcmFtIGZpbGVfbmFtZSBuYW1lIG9mIHRoZSBmaWxlIChzaG91bGQgbm90IGNvbnRhaW4gYW55IHBhdGgpCiMnIEBwYXJhbSBsb2NhbF9mb2xkZXIgcGF0aCBvZiBhIGxvY2FsIGZvbGRlciB3aGVyZSB0aGUgZmlsZSBzaG91bGQgYmUgc3RvcmVkCiMnIEByZXR1cm4gdGhlIGZ1bmN0aW9uIHJldHVybnMgdGhlIHBhdGggb2YgdGhlIGxvY2FsIGZpbGUsIGJ1aWx0IGZyb20gbG9jYWxfZm9sZGVyIGFuZCBmaWxlX25hbWUKIycgQGV4cG9ydMKpCmRvd25sb2FkX29ubHlfb25jZSA8LSBmdW5jdGlvbigKICB1cmxfYmFzZSwgCiAgZmlsZV9uYW1lLAogIGxvY2FsX2ZvbGRlcikgewoKICAjIyBEZWZpbmUgdGhlIHNvdXJjZSBVUkwgIAogIHVybCA8LSBmaWxlLnBhdGgodXJsX2Jhc2UsIGZpbGVfbmFtZSkKICBtZXNzYWdlKCJTb3VyY2UgVVJMXG5cdCIsICB1cmwpCgogICMjIERlZmluZSB0aGUgbG9jYWwgZmlsZQogIGxvY2FsX2ZpbGUgPC0gZmlsZS5wYXRoKGxvY2FsX2ZvbGRlciwgZmlsZV9uYW1lKQogIAogICMjIENyZWF0ZSB0aGUgbG9jYWwgZGF0YSBmb2xkZXIgaWYgaXQgZG9lcyBub3QgZXhpc3QKICBkaXIuY3JlYXRlKGxvY2FsX2ZvbGRlciwgc2hvd1dhcm5pbmdzID0gRkFMU0UsIHJlY3Vyc2l2ZSA9IFRSVUUpCiAgCiAgIyMgRG93bmxvYWQgdGhlIGZpbGUgT05MWSBpZiBpdCBpcyBub3QgYWxyZWFkeSB0aGVyZQogIGlmICghZmlsZS5leGlzdHMobG9jYWxfZmlsZSkpIHsKICAgIG1lc3NhZ2UoIkRvd25sb2FkaW5nIGZpbGUgZnJvbSBzb3VyY2UgVVJMIHRvIGxvY2FsIGZpbGVcblx0IiwgCiAgICAgICAgICAgIGxvY2FsX2ZpbGUpCiAgICBkb3dubG9hZC5maWxlKHVybCA9IHVybCwgZGVzdGZpbGUgPSBsb2NhbF9maWxlKQogIH0gZWxzZSB7CiAgICBtZXNzYWdlKCJMb2NhbCBmaWxlIGFscmVhZHkgZXhpc3RzLCBubyBuZWVkIHRvIGRvd25sb2FkXG5cdCIsIAogICAgICAgICAgICBsb2NhbF9maWxlKQogIH0KICAKICByZXR1cm4obG9jYWxfZmlsZSkKfQpgYGAKCk5vdXMgdMOpbMOpY2hhcmdlb25zIGRldXggZmljaGllcnMgZGFucyB1biBkb3NzaWVyIGxvY2FsIGB+L20zLXN0YXQtUi9wYXZrb3ZpY19hbmFseXNpc2AgKioodm91cyBwb3V2ZXogY2hhbmdlciBsZSBub20gb3UgY2hlbWluIGRhbnMgbGUgY2h1bmsgY2ktZGVzc291cykqKiwgZXQgbGVzIGNoYXJnZW9ucyBkYW5zIGxlcyBkYXRhLmZyYW1lcyBzdWl2YW50czogCgotIERvbm7DqWVzIGJydXRlcyBkZSB0cmFuc2NyaXB0b21lOiBgZmFfZXhwcl9yYXdgCi0gTcOpdGFkb25uw6llczogYGZhX21ldGFgCgpgYGB7ciBkb3dubG9hZF9hbmRfbG9hZH0KIyMgRGVmaW5lIHRoZSByZW1vdGUgVVJMIGFuZCBsb2NhbCBmb2xkZXIKcGF2a292aWNfdXJsIDwtICJodHRwczovL2dpdGh1Yi5jb20vRFUtQmlpL21vZHVsZS0zLVN0YXQtUi9yYXcvbWFzdGVyL3N0YXQtUl8yMDIxL2RhdGEvcGF2a292aWNfMjAxOS8iCgojIyBEZWZpbmUgdGhlIGxvY2FsIGZvbGRlciBmb3IgdGhpcyBhbmFseXNpcyAod2hlcmUgdGhlIGRhdGEgd2lsbCBiZSBkb3dubG9hZGVkIGFuZCB0aGUgcmVzdWx0cyBnZW5lcmF0ZWQpCnBhdmtvdmljX2ZvbGRlciA8LSAifi9tMy1zdGF0LVIvcGF2a292aWNfYW5hbHlzaXMiCgojIyBEZWZpbmUgYSBzdWItZm9sZGVyIGZvciB0aGUgZGF0YQpwYXZrb3ZpY19kYXRhX2ZvbGRlciA8LSBmaWxlLnBhdGgocGF2a292aWNfZm9sZGVyLCAiZGF0YSIpCgojIyBEb3dubG9hZCBhbmQgbG9hZCB0aGUgZXhwcmVzc2lvbiBkYXRhIHRhYmxlCiMjIE5vdGU6IHdlIHVzZSBjaGVjay5uYW1lcz1GQUxTRSB0byBhdm9pZCByZXBsYWNpbmcgaHlwaGVucyBieSBkb3RzCiMjIGluIHNhbXBsZSBuYW1lcywgYmVjYXVzZSB3ZSB3YW50IHRvIGtlZXAgdGhlbSBhcyBpbiB0aGUgCiMjIG9yaWdpbmFsIGRhdGEgZmlsZXMuIAptZXNzYWdlKCJEb3dubG9hZGluZyBGQSB0cmFuc2NyaXB0b21lIGZpbGVcdCIsICJmYV9yYXdfY291bnRzLnRzdi5neiIsCiAgIlxuXHRmcm9tXHQiLCBwYXZrb3ZpY191cmwpCmZhX2V4cHJfZmlsZSA8LSBkb3dubG9hZF9vbmx5X29uY2UoCiAgdXJsX2Jhc2UgPSBwYXZrb3ZpY191cmwsIAogIGZpbGVfbmFtZSA9ICJmYV9yYXdfY291bnRzLnRzdi5neiIsCiAgbG9jYWxfZm9sZGVyID0gcGF2a292aWNfZGF0YV9mb2xkZXIpCgojIyBMb2FkIHRoZSBleHByZXNkc2lvbiB0YWJsZQptZXNzYWdlKCJMb2FkaW5nIEZBIHRyYW5zY3JpcHRvbWUgZGF0YSBmcm9tXG5cdCIsIGZhX2V4cHJfZmlsZSkKZmFfZXhwcl9yYXcgPC0gcmVhZC5kZWxpbShmaWxlID0gZmFfZXhwcl9maWxlLCAKICAgICAgICAgICAgICAgICAgICAgICBoZWFkZXIgPSBUUlVFLCAKICAgICAgICAgICAgICAgICAgICAgICByb3cubmFtZXMgPSAxKQoKIyMgRG93bmxvYWQgdGhlIG1ldGFkYXRhIGZpbGUKbWVzc2FnZSgiRG93bmxvYWRpbmcgRkEgbWV0YWRhdGEgZmlsZVx0IiwgImZhX3RyYW5zY3JpcHRvbWVfbWV0YWRhdGEudHN2IiwKICAiXG5cdGZyb21cdCIsIHBhdmtvdmljX3VybCkKZmFfbWV0YV9maWxlIDwtIGRvd25sb2FkX29ubHlfb25jZSgKICB1cmxfYmFzZSA9IHBhdmtvdmljX3VybCwgCiAgZmlsZV9uYW1lID0gImZhX3RyYW5zY3JpcHRvbWVfbWV0YWRhdGEudHN2IiwKICBsb2NhbF9mb2xkZXIgPSBwYXZrb3ZpY19kYXRhX2ZvbGRlcikKCiMjIExvYWQgdGhlIG1ldGFkYXRhCm1lc3NhZ2UoIkxvYWRpbmcgRkEgbWV0YWRhdGEgZnJvbVxuXHQiLCBmYV9tZXRhX2ZpbGUpCmZhX21ldGEgPC0gcmVhZC5kZWxpbShmaWxlID0gZmFfbWV0YV9maWxlLCAKICAgICAgICAgICAgICAgICAgICAgICBoZWFkZXIgPSBUUlVFLCAKICAgICAgICAgICAgICAgICAgICAgICByb3cubmFtZXMgPSAxKQpgYGAKCk5vdXMgcmVnYXJkb25zIGxhIHN0cnVjdHVyZSBkZSBjaGFxdWUgZGF0YWZyYW1lLgoKYGBge3IgaW5zcGVjdF9kYXRhfQpzdHIoZmFfZXhwcl9yYXcpCnN0cihmYV9tZXRhKQpgYGAKCkxlcyBkZXV4IGZpY2hpZXJzIG5lIGRvbm5lbnQgcGFzIGxlcyBvYnNlcnZhdGlvbnMgZGUgbCfDqWNoYW50aWxsb24gZGFucyBsZSBtw6ptZSBvcmRyZToKCmBgYHtyIGNoZWNrX2RhdGFfb3JkZXJ9CmZhX21ldGEkc2FtcGxlTmFtZSA9PSBuYW1lcyhmYV9leHByX3JhdykKYGBgCgpOb3VzIGxlcyByw6lvcmdhbmlzb25zIGxlcyDDqWNoYW50aWxsb25zIGRhbnMgbCdvcmRyZSBkZSBsJ2V4cMOpcmllbmNlOiBjb25kaXRpb24gbm9ybWFsZSwgcHVpcyBkYXkgMSDDoCAxNCBhdmVjIGxlcyAzIHLDqXBsaWNhdHMuCgpgYGB7ciByZW9kZXJfZGF0YX0Kc2FtcGxlX29yZGVyIDwtIGMocGFzdGUocmVwKGMoIm5vcm1hbCIsICJkYXkxIiwgImRheTIiLCAiZGF5MyIsICJkYXk3IiwgImRheTE0IiksIGVhY2ggPSAzKSwKICAgICAgICAgICAgICAgICAgICAgICAgMTozLCBzZXAgPSAiXyIpKQoKZmFfZXhwcl9yYXcgPC0gZmFfZXhwcl9yYXdbLHNhbXBsZV9vcmRlcl0KZmFfbWV0YSA8LSBmYV9tZXRhW21hdGNoKHNhbXBsZV9vcmRlciwgZmFfbWV0YSRzYW1wbGVOYW1lKSxdCgojIFZpZXcoZmFfbWV0YSkKa2FibGUoZmFfbWV0YSwgY2FwdGlvbiA9ICJNZXRkYXRhIGZvciBQYXZrb3ZvYyBGQSB0cmFuc2NyaXB0b21lIikKYGBgCgo9PiBBaW5zaSwgbm91cyBhdm9ucyB1biBqZXUgZGUgZG9ubsOpZXMgYXZlYyB1biDDqWNoYW50aWxsb24gZGUgYHIgbnJvdyhmYV9tZXRhKWAgb2JzZXJ2YXRpb25zIGV0IGRlcyBkb25uw6llcyBkJ2V4cHJlc3Npb24gZGUgYHIgbnJvdyhmYV9leHByX3JhdylgIGfDqG5lcy4KCgojIyMgU3RhdGlzdGlxdWVzIGRlc2NyaXB0aXZlcwoKRGFucyBsZSB0dXRvcmlhbCBzdXIgbGVzIGRhdGFmcmFtZXMgc3VyIGxlIGpldSBkZSBkb25uw6llcyAidXVvIiAocmVsaXNleiBsZSBjb3JyaWfDqSksIG5vdXMgdm91cyBhdm9ucyBkZW1hbmTDqSBkZSBjcsOpZXIgdW4gZGF0YS5mcmFtZSBxdWkgY29sbGVjdGUgbGVzIHN0YXRpc3RpcXVlcyBwYXIgZ8OobmUgZXQgcGFyIMOpY2hhbnRpbGxvbi4gTm91cyB2b3VzIGRlbWFuZG9ucyBkZSByw6lhbGlzZXIgdW5lIMOpdHVkZSBzaW1pbGFpcmUgc3VyIGxlcyBkb25uw6llcyAiRkEiIGF2YW50IGV0IGFwcsOocyBub3JtYWxpc2F0aW9uIGludGVyLcOpY2hhbnRpbGxvbnMgZGVzIGRvbm7DqWVzLiBMZSBjb2RlIGRlIGxhIHBhcnRpZSBhdmFudCBub3JtYWxpc2F0aW9uIGVzdCBkb25uw6kuCgojIyMjIFBhciDDqWNoYW50aWxsb24gYXZhbnQgbm9ybWFsaXNhdGlvbgoKTm91cyBjcsOpb25zIHVuIGRhdGEuZnJhbWUgbm9tbcOpIGBzYW1wbGVfc3RhdF9wcmVub3JtYCBxdWkgY29tcG9ydGUgdW5lIGxpZ25lIHBhciDDqWNoYW50aWxsb24gZXQgdW5lIGNvbG9ubmUgcGFyIHN0YXRpc3RpcXVlLiBOb3VzIGNhbGN1bG9ucyBsZXMgc3RhdGlzdGlxdWVzIHN1aXZhbnRlcyBzdXIgbGVzIHZhbGV1cnMgbG9nMiBkJ2V4cHJlc3Npb24gZGUgY2hhcXVlIMOpY2hhbnRpbGxvbjoKCi0gbW95ZW5uZQotIMOpY2FydC10eXBlCi0gaW50ZXJ2YWxsZSBpbnRlci1xdWFydGlsZXMKLSBwcmVtaWVyIHF1YXJ0aWxlCi0gbcOpZGlhbmUKLSB0cm9pc2nDqG1lIHF1YXJ0aWxlCi0gbWF4aW11bQotIG5vbWJyZSBkZSB2YWxldXJzIG51bGxlcwoKSWwgZXN0IGFmZmljaMOpIGF2ZWMgbGEgZm9uY3Rpb24gYGthYmxlKClgLiAKCmBgYHtyIHNhbXBsZV9zdGF0X3ByZV9ub3JtfQptZXNzYWdlKCJDb21wdXRpbmcgc2FtcGxlLXdpc2Ugc3RhdGlzdGljcyBvbiByYXcgY291bnRzIikKc2FtcGxlX3N0YXRfcHJlbm9ybSA8LSBkYXRhLmZyYW1lKAogIG1lYW4gPSBhcHBseShmYV9leHByX3JhdywgMiwgbWVhbiwgbmEucm0gPSBUUlVFKSwKICBzZCA9IGFwcGx5KGZhX2V4cHJfcmF3LCAyLCBzZCwgbmEucm0gPSBUUlVFKSwKICBpcXIgPSBhcHBseShmYV9leHByX3JhdywgMiwgSVFSLCBuYS5ybSA9IFRSVUUpLAogIFExID0gYXBwbHkoZmFfZXhwcl9yYXcsIDIsIHF1YW50aWxlLCBwID0gMC4yNSwgbmEucm0gPSBUUlVFKSwKICBtZWRpYW4gPSBhcHBseShmYV9leHByX3JhdywgMiwgbWVkaWFuLCBuYS5ybSA9IFRSVUUpLAogIFEzID0gYXBwbHkoZmFfZXhwcl9yYXcsIDIsIHF1YW50aWxlLCBwID0gMC43NSwgbmEucm0gPSBUUlVFKSwKICBtYXggPSBhcHBseShmYV9leHByX3JhdywgMiwgbWF4LCBuYS5ybSA9IFRSVUUpLAogIG51bGwgPSBhcHBseShmYV9leHByX3JhdyA9PSAwLCAyLCBzdW0sIG5hLnJtID0gVFJVRSkKKQoKa2FibGUoc2FtcGxlX3N0YXRfcHJlbm9ybSwgY2FwdGlvbiA9ICJTYW1wbGUtd2lzZSBzdGF0aXN0aWNzIGJlZm9yZSBub3JtYWxpc2F0aW9uLiIpCmBgYAoKIyMjIyBQYXIgZ8OobmUgYXZhbnQgbm9ybWFsaXNhdGlvbgoKTm91cyBjcsOpb25zIGNpLWRlc3NvdXMgdW4gZGF0YS5mcmFtZSBub21tw6kgYGdlbmVfc3RhdF9wcmVub3JtYCBxdWkgY29tcG9ydGUgdW5lIGxpZ25lIHBhciBnw6huZSBldCB1bmUgY29sb25uZSBwYXIgc3RhdGlzdGlxdWUuIE5vdXMgY2FsY3Vsb25zIGxlcyBzdGF0aXN0aXF1ZXMgc3VpdmFudGVzIHN1ciBsZXMgdmFsZXVycyBsb2cyIGRlIGNoYXF1ZSBnw6huZS4KCi0gbW95ZW5uZQotIG3DqWRpYW5lCi0gw6ljYXJ0LXR5cGUKLSBwcmVtaWVyIHF1YXJ0aWxlCi0gdHJvaXNpw6htZSBxdWFydGlsZQotIG1heGltdW0KLSBub21icmUgZGUgdmFsZXVycyBudWxsZXMKLSBpbnRlcnZhbGxlIGludGVyLXF1YXJ0aWxlcwoKQ2VzIHLDqXN1bHRhdHMgc29udCBzdG9ja8OpcyBkYW5zIHVuIGRhdGEuZnJhbWUgYXZlYyAxIGxpZ25lIHBhciDDqWNoYW50aWxsb24gZXQgMSBjb2xvbm5lIHBhciBzdGF0aXN0aXF1ZS4gTm91cyBhZmZpY2hvbnMgbGVzIGxpZ25lcyAxMDAgw6AgMTA5IGRlIGNlIHRhYmxlYXUgZGUgc3RhdGlzdGlxdWVzIGF2ZWMgbGEgZm9uY3Rpb24gYGthYmxlKClgLgoKYGBge3IgZ2VuZV9zdGF0X3ByZV9ub3JtfQojIyBHZW5lLXdpc2Ugc3RhdGlzdGljcyBmb3IgdGhlIHJhdyBjb3VudHMgKHdpbGwgYmUgdXNlZCBmb3Igbm9ybWFsaXNhdGlvbikKbWVzc2FnZSgiQ29tcHV0aW5nIGdlbmUtd2lzZSBzdGF0aXN0aWNzIG9uIHJhdyBjb3VudHMiKQpnZW5lX3N0YXRfcHJlbm9ybSA8LSBkYXRhLmZyYW1lKAogIG1lYW4gPSBhcHBseShmYV9leHByX3JhdywgMSwgbWVhbiwgbmEucm0gPSBUUlVFKSwKICBzZCA9IGFwcGx5KGZhX2V4cHJfcmF3LCAxLCBzZCwgbmEucm0gPSBUUlVFKSwKICBpcXIgPSBhcHBseShmYV9leHByX3JhdywgMSwgSVFSLCBuYS5ybSA9IFRSVUUpLAogIFExID0gYXBwbHkoZmFfZXhwcl9yYXcsIDEsIHF1YW50aWxlLCBwID0gMC4yNSwgbmEucm0gPSBUUlVFKSwKICBtZWRpYW4gPSBhcHBseShmYV9leHByX3JhdywgMSwgbWVkaWFuLCBuYS5ybSA9IFRSVUUpLAogIFEzID0gYXBwbHkoZmFfZXhwcl9yYXcsIDEsIHF1YW50aWxlLCBwID0gMC43NSwgbmEucm0gPSBUUlVFKSwKICBtYXggPSBhcHBseShmYV9leHByX3JhdywgMSwgbWF4LCBuYS5ybSA9IFRSVUUpLAogIG51bGwgPSBhcHBseShmYV9leHByX3JhdyA9PSAwLCAxLCBzdW0sIG5hLnJtID0gVFJVRSkKKQoKa2FibGUoZ2VuZV9zdGF0X3ByZW5vcm1bMTAwOjEwOSwgXSwgY2FwdGlvbiA9ICJHZW5lLXdpc2Ugc3RhdGlzdGljcyBiZWZvcmUgbm9ybWFsaXNhdGlvbiIpCmBgYAoKIyMgMi4gRmlsdHJhZ2UgZXQgbm9ybWFsaXNhdGlvbiBkZXMgZG9ubsOpZXMKCioqKlZvdXMgbidhdmV6IHJpZW4gw6AgY29kZXIgaWNpLiBMZSBjb2RlIGVzdCBmb3VybmkuKioqCgpJbCBleGlzdGUgcGx1c2lldXJzIGZhw6dvbnMgZGUgbm9ybWFsaXNlciBsZXMgZG9ubsOpZXMgZGUgdHJhbnNjcmlwdG9tZSAgdnVlcyBkYW5zIGxlcyBtb2R1bGVzIDQgZXQgNSAoY2YuIHRvdGFsIGNvdW50cywgcXVhbnRpbGVzLCBUTU0sIFJMRSwgbGltbWEgdm9vbSwuLi4pLCBtYWlzIG5vdXMgYXZvbnMgY2hvaXNpIGljaSB1bmUgc29sdXRpb24gc2ltcGxlIHRvdXQgZW4gw6l0YW50IHJvYnVzdGUgcG91ciBub3JtYWxpc2VyIGxlcyBkb25uw6llcyBlbiBzdGFuZGFyZGlzYW50IGxlIDPDqG1lIHF1YW50aWxlLiAKCkxhIG3DqXRob2RlIGNob2lzaWUgaWNpIGNvbnNpc3RlIMOgIDoKCjEuICoqRWNhcnRlciBsZXMgZ8OobmVzICJub24tZMOpdGVjdMOpcyIqKiwgYydlc3Qtw6AtZGlyZSBjZXV4IGF5YW50IGRlcyB2YWxldXJzIG51bGxlcyBkYW5zIGF1IG1vaW5zIDkwJSBkZXMgw6ljaGFudGlsbG9ucy4KCjIuICoqRWNhcnRlciBsZXMgZ8OobmVzIMOgIHBlaW5lIGV4cHJpbcOpcyoqLCBjJ2VzdC3DoC1kaXJlIGNldXggYXlhbnQgdW5lIHZhbGV1ciBtb3llbm5lIDwgMTAgKGFyYml0cmFpcmVtZW50KS4KCjMuICoqU3RhbmRhcmRpc2VyIGxlcyDDqWNoYW50aWxsb25zICoqc3VyIGxlIDPDqG1lIHF1YXJ0aWxlIGRlcyBnw6huZXMgcmVzdGFudHM6IG9uIGRpdmlzZSBsZXMgY29tcHRhZ2VzIGJydXRzIHBhciBsZSAzw6htZSBxdWFydGlsZSBkZSBsJ8OpY2hhbnRpbGxvbiBldCBvbiBtdWx0aXBsaWUgcGFyIGxlIDPDqG1lIHF1YXJ0aWxlIGRlIGwnZW5zZW1ibGUgZGVzIMOpY2hhbnRpbGxvbnMuCgo0LiAqKk5vcm1hbGlzZXIgbGVzIGNvbXB0YWdlcyoqIChhdSBzZW5zIHByb3ByZSwgYydlc3Qtw6AtZGlyZSByYXBwcm9jaGVyIGxldXIgZGlzdHJpYnV0aW9uIGRlIGxhIGRpc3RyaWJ1dGlvbiBnYXVzc2llbm5lKSBwYXIgdW5lIHRyYW5zZm9ybWF0aW9uIGxvZ2FyaXRobWlxdWUgKGxvZzIpLiAKCk5vdXMgZm91cm5pc3NvbnMgY2ktZGVzc291cyBsZSBjb2RlLgoKIyMjIEZpbHRyYWdlIDogw6lsaW1pbmF0aW9uIGRlcyBnw6huZXMgbm9uIGTDqXRlY3TDqXMgb3Ugw6AgcGVpbmUgZXhwcmltw6lzCgpgYGB7ciBnZW5lX2ZpbHRlcmluZ30KIyMgRGF0YSBmaWx0ZXJpbmc6IGdlbmVzIGhhdmluZyBhdCBsZWFzdCA5MCUgbnVsbCB2YWx1ZXMKbWVzc2FnZSgiRmlsdGVyaW5nIHVuZGV0ZWN0ZWQgZ2VuZXMiKQp1bmRldGVjdGVkX2dlbmVzIDwtIGdlbmVfc3RhdF9wcmVub3JtJG51bGwgPj0gbmNvbChmYV9leHByX3JhdykgKiAwLjkKcHJpbnQocGFzdGUwKCJVbmRldGVjdGVkIGdlbmVzIChudWxsIGluID49IDkwJSBzYW1wbGVzKTogIiwgc3VtKHVuZGV0ZWN0ZWRfZ2VuZXMpKSkKCiMjIERhdGEgZmlsdGVyaW5nOiBnZW5lcyBoYXZpbmcgYSBtZWFuIGV4cHJlc3Npb24gPCAxMAptZXNzYWdlKCJGaWx0ZXJpbmcgYmFyZWx5IGV4cHJlc3NlZCBnZW5lcyIpCmJhcmVseV9leHByZXNzZWRfZ2VuZXMgPC0gZ2VuZV9zdGF0X3ByZW5vcm0kbWVhbiA8IDEwCnByaW50KHBhc3RlMCgiQmFyZWx5IGV4cHJlc3NlZCBnZW5lcyAobWVhbiA8IDEwKTogIiwgc3VtKGJhcmVseV9leHByZXNzZWRfZ2VuZXMpKSkKCiMjIEFwcGx5IGZpbHRlcmluZyBvbiBib3RoIGNyaXRlcmlhCmRpc2NhcmRlZF9nZW5lcyA8LSB1bmRldGVjdGVkX2dlbmVzIHwgYmFyZWx5X2V4cHJlc3NlZF9nZW5lcwpwcmludChwYXN0ZTAoIkRpc2NhcmRlZCBnZW5lczogIiwgc3VtKGRpc2NhcmRlZF9nZW5lcykpKQprZXB0X2dlbmVzIDwtICFkaXNjYXJkZWRfZ2VuZXMKcHJpbnQocGFzdGUwKCJLZXB0IGdlbmVzOiAiLCBzdW0oa2VwdF9nZW5lcykpKQoKIyMgR2VuZXMgYWZ0ZXIgZmlsdGVyaW5nCmZhX2V4cHJfZmlsdGVyZWQgPC0gZmFfZXhwcl9yYXdba2VwdF9nZW5lcywgXQoKYGBgCgojIyMgU3RhbmRhcmRpc2F0aW9uIGVudHJlIMOpY2hhbnRpbGxvbnMKCk5vdXMgYXBwbGlxdW9ucyBpY2kgdW5lIG3DqXRob2RlIHNpbXBsZSBtYWlzIGVmZmljYWNlIGRlIHN0YW5kYXJkaXNhdGlvbiBlbiBhcHBsaXF1YW50IHVuIGZhY3RldXIgbXVsdGlwbGljYXRpZiBxdWkgcmFtw6huZSB0b3VzIGxlcyDDqWNoYW50aWxsb25zIGF1IG3Dqm1lIHRyb2lzacOobWUgcXVhcnRpbGUgKCRRMyQpLiAKCmBgYHtyIG5vcm1hbGlzYXRpb25fcTN9CiMjIyMgSW50ZXItc2FtcGxlIHN0YW5kYXJkaXNhdGlvbiBvbiB0aGUgUTMgb2YgcmF3IGNvdW50cyAjIyMjCnRvdGFsX3EzIDwtIHF1YW50aWxlKHVubGlzdChmYV9leHByX2ZpbHRlcmVkKSwgcHJvYnMgPSAwLjc1KQpzYW1wbGVfc3RhdF9wcmVub3JtJFEzX2ZpbHRlcmQgPC0gYXBwbHkoZmFfZXhwcl9maWx0ZXJlZCwgMiwgcXVhbnRpbGUsIHByb2JzID0gMC43NSkKc2FtcGxlX3N0YXRfcHJlbm9ybSRzY2FsZV9mYWN0b3IgPC0gMSAvIHNhbXBsZV9zdGF0X3ByZW5vcm0kUTNfZmlsdGVyZCAqIHRvdGFsX3EzCgojIyBBcHBseSBzdGFuZGFyZGlzYXRpb24KZmFfZXhwcl9zdGFuZGFyZCA8LSB0KHQoZmFfZXhwcl9maWx0ZXJlZCkgKiB1bmxpc3Qoc2FtcGxlX3N0YXRfcHJlbm9ybSRzY2FsZV9mYWN0b3IpKQojIyBDaGVjayAzcmQgcXVhbnRpbGUgYWZ0ZXIgc3RhbmRhcmRpc2F0aW9uCmthYmxlKGFwcGx5KGZhX2V4cHJfc3RhbmRhcmQsIDIsIHF1YW50aWxlLCBwcm9icyA9IDAuNzUpLCAKICAgICAgY29sLm5hbWVzID0gIlEzX3N0YW5kYXJkaXNlZCIsIAogICAgICBjYXB0aW9uID0gIlRoaXJkIHF1YXJ0aWxlIG9mIHRoZSBmaWx0ZXJlZCBjb3VudHMgYWZ0ZXIgaW50ZXItc2FtcGxlIHN0YW5kYXJkaXNhdGlvbiBvZiB0aGUgdGhpcmQgcXVhcnRpbGVzLiAiKQojYm94cGxvdChmYV9leHByX3N0YW5kYXJkLCBob3Jpem9udGFsID0gVFJVRSkKYGBgCgojIyMgVHJhbnNmb3JtYXRpb24gbG9nMgoKTm91cyBhcHBsaXF1b25zIHVuZSB0cmFuc2Zvcm1hdGlvbiBlbiBsb2cyIGRlcyBkb25uw6llcyBicnV0ZXMsIGFwcsOocyBhdm9pciBham91dMOpIHVuIGVwc2lsb24gJFxlcHNpbG9uID0gMSQgKGxlcyB2YWxldXJzIG51bGxlcyBzZXJvbnQgZG9uYyByZXByw6lzZW50w6llcyBwYXIgdW4gbG9nMihjb3VudHMpIHZhbGFudCAkMCQuIE5vdXMgc3RvY2tvbnMgbGUgcsOpc3VsdGF0IGRhbnMgdW4gZGF0YS5mcmFtZSBub21tw6kgYGZhX2V4cHJfbG9nMmAuCgpOb3VzIGFmZmljaG9ucyB1biBmcmFnbWVudCBkZXMgdGFibGVhdXggYGZhX2V4cHJfcmF3YCBldCBgZmFfZXhwcl9sb2cyYCBlbiBzw6lsZWN0aW9ubmFudCBsZXMgbGlnbmVzIDEwMCDDoCAxMDkgZXQgbGVzIGNvbG9ubmVzIDUgw6AgMTAsIGFmaW4gZGUgbm91cyBhc3N1cmVyIHF1ZSBsYSB0cmFuc2Zvcm1hdGlvbiBlbiBsb2cyIGEgYmllbiBmb25jdGlvbm7DqS4gCgpgYGB7ciBsb2cyX3RyYW5zZm9ybX0KIyMgTG9nMiB0cmFuc2Zvcm1hdGlvbiBvZiB0aGUgdHJhbnNjcmlwdG9tZSBkYXRhCmVwc2lsb24gPC0gMQpmYV9leHByX2xvZzIgPC0gbG9nMihmYV9leHByX3N0YW5kYXJkICsgZXBzaWxvbikKIyBkaW0oZmFfZXhwcl9sb2cyKQojIFZpZXcoaGVhZChmYV9leHByX2xvZzIpKQoKIyMgRGlzcGxheSBvZiBhIGZyYWdtZW50IG9mIHRoZSBkYXRhIGJlZm9yZSBhbmQgYWZ0ZXIgbG9nMiB0cmFuc2Zvcm1hdGlvbgprYWJsZShmYV9leHByX3Jhd1sxMDA6MTA5LCA1OjEwXSwgY2FwdGlvbiA9ICJGcmFnbWVudCBkZXMgZG9ubsOpZXMgdHJhbnNjcmlwdG9taXF1ZXMgYnJ1dGVzIikKa2FibGUoZmFfZXhwcl9sb2cyWzEwMDoxMDksIDU6MTBdLCBjYXB0aW9uID0gIkZyYWdtZW50IGRlcyBkb25uw6llcyB0cmFuc2NyaXB0b21pcXVlcyBhcHLDqHMgdHJhbnNmb3JtYXRpb24gbG9nMiIpCmBgYAoKIyMgMy4gU3RhdGlzdGlxdWVzIGRlc2NyaXB0aXZlcyBzdXIgbGVzIGRvbm7DqWVzIG5vcm1hbGlzw6llcwoKKioqQSB2b3VzIGRlIGpvdWVyISoqKgoKIyMjIFN0YXRpc3RpcXVlcyBwYXIgZ8OobmUgYXByw6hzIG5vcm1hbGlzYXRpb24KCkfDqW7DqXJleiB1biBkYXRhLmZyYW1lIG5vbW3DqWUgYGdlbmVfc3RhdF9ub3JtYCBhdmVjIHVuZSBsaWduZSBwYXIgZ8OobmUgw6AgcGFydGlyIGR1IHRhYmxlYXUgZGUgZG9ubsOpZXMgbm9ybWFsaXPDqWVzLCBhdmVjIGxlcyBzdGF0aXN0aXF1ZXMgc3VpdmFudGVzICh1bmUgc3RhdGlzdGlxdWUgcGFyIGNvbG9ubmUpOgoKLSBtb3llbm5lCi0gdmFyaWFuY2UKLSDDqWNhcnQtdHlwZQotIGNvZWZmaWNpZW50IGRlIHZhcmlhdGlvbiAow6ljYXJ0LXR5cGUgZGl2aXPDqSBwYXIgbGEgbW95ZW5uZSkKLSBpbnRlcnZhbGxlIGludGVyLXF1YXJ0aWxlcwotIG1pbmltdW0KLSBtw6lkaWFuZQotIG1heGltdW0KCmBgYHtyIGdlbmVfc3RhdF9wb3N0X25vcm19CiMjIEdlbmUtd2lzZSBzdGF0aXN0aWNzIGFmdGVyIG5vcm1hbGlzYXRpb24KbWVzc2FnZSgiQ29tcHV0aW5nIGdlbmUtd2lzZSBzdGF0aXN0aWNzIG9uIGxvZzItdHJhbnNmb3JtZWQgYW5kIG5vcm1hbGlzZWQgY291bnRzIikKZ2VuZV9zdGF0X25vcm0gPC0gZGF0YS5mcmFtZShtZWFuID0gYXBwbHkoZmFfZXhwcl9sb2cyLCAxLCBtZWFuKSwKICAgICAgICAgICAgICAgICAgICAgICAgICAgICB2YXIgPSBhcHBseShmYV9leHByX2xvZzIsIDEsIHZhciksCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgc2QgPSBhcHBseShmYV9leHByX2xvZzIsIDEsIHNkKSwKICAgICAgICAgICAgICAgICAgICAgICAgICAgICBpcXIgPSBhcHBseShmYV9leHByX2xvZzIsIDEsIElRUiksCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgbWluID0gYXBwbHkoZmFfZXhwcl9sb2cyLCAxLCBtaW4pLAogICAgICAgICAgICAgICAgICAgICAgICAgICAgIG1lZCA9IGFwcGx5KGZhX2V4cHJfbG9nMiwgMSwgbWVkaWFuKSwKICAgICAgICAgICAgICAgICAgICAgICAgICAgICBtYXggPSBhcHBseShmYV9leHByX2xvZzIsIDEsIG1heCkpCgojIEFqb3V0IGR1IGNvZWZmaWNpZW50IGRlIHZhcmlhdGlvbgpnZW5lX3N0YXRfbm9ybSRjb2VmX3ZhciA8LSAoZ2VuZV9zdGF0X25vcm0kc2QgLyBnZW5lX3N0YXRfbm9ybSRtZWFuKQoKa2FibGUoZ2VuZV9zdGF0X25vcm1bMTAwOjEwOSxdLCBjYXB0aW9uID0gIlN0YXRpc3RpcXVlcyBwYXIgZ8OobmUgc3VyIGxlcyBkb25uw6llcyBub3JtYWxpc8OpZXMiKQpgYGAKCiMjIyBBbm5vdGF0aW9uIGRlcyBnw6huZXMKCkNoYXF1ZSBnw6huZSDDqXRhbnQgZG9ubsOpIHBhciBzb24gaWRlbnRpZmlhbnQgZGFucyBsYSBiYXNlIGRlIGRvbm7DqWVzIEVOU0VNQkwgdm91cyB1dGlsaXNlcmV6IGxlICoqcGFxdWV0IGJpb21hUnQgZGUgYmlvY29uZHVjdG9yKiogcG91ciBham91dGVyIGRlcyBhbm5vdGF0aW9ucyA6IHN5bWJvbGUsIGNocm9tb3NvbWUsIGNvb3Jkb25uw6llcyBnw6lub21pcXVlcywgYnJpbi4gClN1aXZleiBwYXMgw6AgcGFzIGxhIG3DqXRob2RlIHByb3Bvc8OpZSAoKioqY2VydGFpbmVzIMOpdGFwZXMgcGV1dmVudCBwcmVuZHJlIHF1ZWxxdWVzIG1pbnV0ZXMqKiopOgoKIC0gY2hhcmdleiBsZSBwYXF1ZXQgYmlvbWFSdCwgdm9pcmUgaW5zdGFsbGVyLWxlIHVuaXF1ZW1lbnQgc2kgbsOpY2Vzc2FpcmUuIEluZGlxdWV6IGxlIGNvZGUgw6AgbCdlbXBsYWNlbWVudCBhZMOpcXVhdCBkYW5zIGxlIC5SbWQuCgogLSBzw6lsZWN0aW9ubmV6IGxhIGJhc2UgZGUgZG9ubsOpZXMgRU5TRU1CTCBhdmVjIGxhIGZvbmN0aW9uIGB1c2VNYXJ0KClgLiBBdHRlbnRpb24gw6AgY2hvaXNpciBsZSBib24gZ8Opbm9tZSBhdmVjIGwnYWd1bWVudCBgZGF0YXNldGA6ICJtbXVzY3VsdXNfZ2VuZV9lbnNlbWJsIgogCiAtIGF2ZWMgbGEgZm9uY3Rpb24gYGdldEJNKClgIHLDqWN1cMOpcmV6IGRlIGxhIGJhc2UgZGUgZG9ubsOpZXMgRU5TRU1CTCBsZXMgY2hhbXBzIGRlbWFuZMOpcyAoKioqcG91ciBzeW1ib2xlIHV0aWxpc2V6IGV4dGVybmFsX2dlbmVfbmFtZSoqKikgZW4gYXBwbGlxdWFudCAiZW5zZW1ibF9nZW5laWQiIHBvdXIgbCdhZ3VtZW50IGBmaWx0ZXJgIGV0IGVuIGluZGlxdWFudCBwb3VyIGwnYXJndW1lbnQgYHZhbHVlc2AgbGUgdmVjdGV1ciBkZXMgaWRlbnRpZmlhbnRzIGRlcyBnw6huZXMgcHLDqXNlbnRzIGRhbnMgbGUgZGF0YWZyYW1lIGBnZW5lX3N0YXRfbm9ybWAuIFZvdXMgb2J0ZW5leiB1biBkYXRhZnJhbWUuCiAKQSBwcsOpc2VudCwgYWpvdXRleiBhdSBkYXRhZnJhbWUgYGdlbmVfc3RhdF9ub3JtYCBlbiAxw6hyZXMgY29sb25uZXMgbGVzIGFubm90YXRpb25zIHJldHJvdXbDqWVzIGdyw6JjZSDDoCBiaW9tYVJ0LiBBdHRlbnRpb24sIGNlcnRhaW5zIGfDqG5lcyBuZSBzb250IHBhcyByZXRyb3V2w6lzIGRhbnMgbGEgdmVyc2lvbiBkJ0VOU0VNQkwgc3VyIGJpb21hUnQgZG9uYyBsYWlzc2V6IGRlcyBOQSBjb21tZSBkb25uw6llcyBtYW5xdWFudGVzIGRhbnMgY2UgY2FzLiBOb3VzIHZvdXMgcmVjb21tYW5kb25zIGQndXRpbGlzZXIgbGEgZnVuY3Rpb24gYG1lcmdlKClgIGRlIFIgYmFzZSBvdSBiaWVuIGBsZWZ0X2pvaW4oKWAgZGUgYGRwbHlyYCBwb3VyIGZ1c2lvbm5lciBsZXMgZGV1eCBkYXRhZnJhbWVzIGVuIHVuIHNldWwuCiAKYGBge3IgZ2VuZV9hbm5vdGF0aW9uc30KIyMjIEdlbmUgYW5ub3RhdGlvbnMgIyMjIwptZXNzYWdlKCJHZXR0aW5nIGdlbmUgYW5ub3RhdGlvbnMiKQoKIyBDb25uZWN0aW5nIHRvIEVuc2VtYmwgQmlvbWFydCBkYXRhYmFzZSBhbmQgc2VsZWN0aW5nIG1vdXNlIGRhdGFzZXQKZW5zZW1ibCA8LSB1c2VNYXJ0KCJlbnNlbWJsIiwgZGF0YXNldD0ibW11c2N1bHVzX2dlbmVfZW5zZW1ibCIpCgojIExldCdzIHRha2UgYSBsb29rIGEgdGhlIG5hbWVzIG9mIG91ciBkZXNpcmVkIGF0dHJpYnV0ZXMgaW4gdGhlIGRhdGFzZXQKa2FibGUobGlzdEF0dHJpYnV0ZXMoZW5zZW1ibClbMToxNSxdKQoKIyBMZXQncyBsb29rIGF0IHRoZSBmaWx0ZXJzIGxpc3QgdG8gcGljayB0aGUgZGVzaXJlZCBmaWx0ZXIKa2FibGUobGlzdEZpbHRlcnMoZW5zZW1ibClbNDU6NTAsXSkKCgojIyBDb2xsZWN0aW5nIGFubm90YXRpb25zCgojIEkgYWxzbyByZWNvdmVyIHRoZSBhdHRyaWJ1dGUgImVuc2VtYmxfZ2VuZV9pZCIgdG8gYmUgYWJsZSB0byBtZXJnZSB0aGUgZGF0YWZyYW1lcyBsYXRlcgpnZXRCTShhdHRyaWJ1dGVzID0gYygiZW5zZW1ibF9nZW5lX2lkIiwgImV4dGVybmFsX2dlbmVfbmFtZSIsICJjaHJvbW9zb21lX25hbWUiLCAKICAgICAgICAgICAgICAgICAgICAgInN0YXJ0X3Bvc2l0aW9uIiwgImVuZF9wb3NpdGlvbiIsICJzdHJhbmQiKSwKICAgICAgZmlsdGVyID0gImVuc2VtYmxfZ2VuZV9pZCIsCiAgICAgIHZhbHVlcyA9IHJvdy5uYW1lcyhnZW5lX3N0YXRfbm9ybSksCiAgICAgIG1hcnQgPSBlbnNlbWJsKSAtPiBhbm5vdGF0aW9uc19kZgoKCiMjIE1lcmdpbmcgdGhlIGRhdGFmcmFtZXMKCiMgYWRkaW5nIHRoZSBjb2x1bW4gImVuc2VtYmxfZ2VuZV9pZCIgdG8gdGhlIGdlbmVfc3RhdF9ub3JtIGRhdGFmcmFtZSBmb3IgZWFzaWVyIG1lcmdpbmcKZ2VuZV9zdGF0X25vcm0kZW5zZW1ibF9nZW5lX2lkIDwtIHJvdy5uYW1lcyhnZW5lX3N0YXRfbm9ybSkKCiMgTWVyZ2Ugd2l0aCBkcGx5ciwgYWx0aG91Z2ggSSB1c2UgcmlnaHRfam9pbiBub3QgbGVmdF9qb2luCmdlbmVfc3RhdF9ub3JtIDwtIHJpZ2h0X2pvaW4oYW5ub3RhdGlvbnNfZGYsIGdlbmVfc3RhdF9ub3JtLCAKICAgICAgICAgICAgICAgICAgICAgICAgICAgIGtlZXAgPSBGQUxTRSkKCiMgTnVtYmVyIG9mIGdlbmVzIGZvciB3aGljaCBubyBhbm5vdGF0aW9uIGNvdWxkIGJlIHJlY292ZXJlZCBmcm9tIEVuc2VtYmwKbGVuZ3RoKGdlbmVfc3RhdF9ub3JtW2lzLm5hKGdlbmVfc3RhdF9ub3JtKV0pCmBgYAoKKipDaGFsbGVuZ2UgZmFsY3VsdGF0aWY6KioKClLDqW9yZG9ubmV6IGxlcyBnw6huZXMgcGFyIHBvc2l0aW9uIGfDqW5vbWlxdWUgZXQgYWZmaWNoZXogbGVzIGxpZ25lcyA1IHByZW1pw6hyZXMgZXQgIDUgZGVybmnDqHJlcyBsaWduZXMgZGUgY2UgdGFibGVhdSBkZSBzdGF0aXN0aXF1ZXMuIAoKYGBge3Igc29ydF9nZW5lc19ieV9jaHJvbX0KIyMjIyBTb3J0aW5nIGdlbmVzIGJ5IGNocm9tb3NvbWUgIyMjIwptZXNzYWdlKCJTb3J0aW5nIGdlbmVzIGJ5IGNocm9tb3NvbWUiKQoKa2FibGUoZ2VuZV9zdGF0X25vcm1bNTA6NjAsIDE6Nl0sIGNhcHRpb24gPSAiRGF0YWZyYW1lIGF2YW50IHRyaSIpCmdlbmVfc3RhdF9ub3JtIDwtIGFycmFuZ2UoZ2VuZV9zdGF0X25vcm0sIGNocm9tb3NvbWVfbmFtZSwgc3RhcnRfcG9zaXRpb24pCmthYmxlKGdlbmVfc3RhdF9ub3JtWzUwOjYwLCAxOjZdLCBjYXB0aW9uID0gIkRhdGFmcmFtZSBhcHLDqHMgdHJpIHBhciBwb3NpdGlvbiBnw6lub21pcXVlLiIpCgpoZWFkKGdlbmVfc3RhdF9ub3JtLCA1KQp0YWlsKGdlbmVfc3RhdF9ub3JtLCA1KQpgYGAKCgojIyMgRGlzdHJpYnV0aW9uIGRlcyBkb25uw6llcwoKLSBEZXNzaW5leiBzb3VzIGZvcm1lIGQndW4gaGlzdG9ncmFtbWUgbGEgZGlzdHJpYnV0aW9uIGRlcyB2YWxldXJzIGFwcsOocyBub3JtYWxpc2F0aW9uICh0b3VzIMOpY2hhbnRpbGxvbnMgY29uZm9uZHVzKQoKYGBge3IgZmFfZXhwcl9ub3JtX2Rpc3RyaWIsIGZpZy53aWR0aD04LCBmaWcuaGVpZ2h0PTUsIG91dC53aWR0aD0iNzAlIiwgZmlnLmNhcD0iRGlzdHJpYnV0aW9uIG9mIGFsbCBub3JtYWxpc2VkIGNvdW50cyJ9CgpwYXIobWZyb3c9YygxLDEpKQpoaXN0KGZhX2V4cHJfbG9nMiwKICAgICBicmVha3MgPSAxMDAsCiAgICAgY2V4LmF4aXMgPSAwLjcsCiAgICAgbGFzID0gMSwKICAgICBjb2wgPSAic2t5Ymx1ZSIsCiAgICAgeGxhYiA9ICJub3JtYWxpc2VkIGNvdW50cyIsCiAgICAgbWFpbiA9ICJEaXN0cmlidXRpb24gb2YgYWxsIG5vcm1hbGlzZWQgY291bnRzIikKYGBgCgotIERlc3NpbmV6IHVuIGJveCBwbG90IHBhciDDqWNoYW50aWxsb24gYXZhbnQgKHNhbnMgZXQgYXByw6hzIG5vcm1hbGlzYXRpb24gZGUgbGEgZGlzdHJpYnV0aW9uIGVuIGxvZzIpIGV0IGFwcsOocyBzdGFuZGFyZGlzYXRpb24gKG5vcm1hbGlzYXRpb24gaW50ZXItw6ljaGFudGlsbG9uKS4gQ29tbWVudGV6IGxhIGZhw6dvbiBkb250IGwnZWZmZXQgZGUgbGEgKGRlcykgbm9ybWFsaXNhdGlvbihzKSBhcHBhcmHDrnQgc3VyIGNlcyBncmFwaGlxdWVzLiAKCmBgYHtyIGJveHBsb3RzX25vcm1hbGlzYXRpb25faW1wYWN0LCBmaWcud2lkdGg9MTAsIGZpZy5oZWlnaHQ9MTIsIG91dC53aWR0aD0iMTAwJSIsIGZpZy5jYXA9IkJveHBsb3RzIG9ic2VydmF0aW9uIG9mIHRoZSBub3JtYWxpc2F0aW9uIHByb2NlZHVyZXMuIn0KIyMjIyBCb3ggcGxvdHMgdG8gc2hvdyBub3JtYWxpc2F0aW9uIGltcGFjdCAjIyMjCgojIFJhdyBkYXRhCmJveHBsb3QoZmFfZXhwcl9yYXcsCiAgICAgICAgbWFpbiA9ICJSYXcgZXhwcmVzc2lvbiIsCiAgICAgICAgaG9yaXpvbnRhbCA9IFRSVUUsCiAgICAgICAgY29sID0gZmFfbWV0YSRjb2xvciwKICAgICAgICBjZXggPSAwLjUsCiAgICAgICAgY2V4LmF4aXMgPSAwLjgsCiAgICAgICAgbGFzID0gMSkKCiMgV2l0aCBmaWx0ZXJlZCBleHByZXNzaW9uIGFuZCBsb2cyIHRyYW5zZm9ybWF0aW9uCmJveHBsb3QobG9nMihmYV9leHByX2ZpbHRlcmVkICsgMSksCiAgICAgICAgbWFpbiA9ICJGaWx0ZXJlZCBhbmQgbG9nMiB0cmFuc2Zvcm1lZCBleHByZXNzaW9uIiwKICAgICAgICBob3Jpem9udGFsID0gVFJVRSwKICAgICAgICBjb2wgPSBmYV9tZXRhJGNvbG9yLAogICAgICAgIGNleCA9IDAuNSwKICAgICAgICBjZXguYXhpcyA9IDAuOCwKICAgICAgICBsYXMgPSAxKQoKIyBXaXRoIGZpbHRlcmVkLCBzdGFuZGFyZGlzZWQgYWNyb3NzIHNhbXBsZXMgYW5kIGxvZzIgdHJhbnNmb3JtYXRpb24KYm94cGxvdChmYV9leHByX2xvZzIsCiAgICAgICAgbWFpbiA9ICJTdGFuZGFyZGlzZWQgYW5kIGxvZzIgdHJhbnNmb3JtZWQgZXhwcmVzc2lvbiIsCiAgICAgICAgaG9yaXpvbnRhbCA9IFRSVUUsCiAgICAgICAgY29sID0gZmFfbWV0YSRjb2xvciwKICAgICAgICBjZXggPSAwLjUsCiAgICAgICAgY2V4LmF4aXMgPSAwLjgsCiAgICAgICAgbGFzID0gMSkKYGBgCgpBdmVjIGxlcyBkb25uw6llcyBkJ2V4cHJlc3Npb24gYnJ1dGVzLCBsZXMgdmFsZXVycyBzb250IMOpdGFsw6llcyBhdmVjIHF1ZWxxdWVzIG91dGxpZXJzIGV4dHLDqm1lbWVudCBleHByaW3DqXMgZXQgdW5lIGdyYW5kZSBtYWpvcml0w6kgZGUgZ8OobmVzIHBhcyBvdSBwZXUgZXhwcmltw6lzLgoKTGUgZmlsdHJhZ2UgZGUgY2VzIGfDqG5lcyBwZXUgb3UgcGFzIGV4cHJpbcOpcyBldCBsYSB0cmFuc2Zvcm1hdGlvbiBlbiBsb2cyIHBlcm1ldCBkZSAicmVncm91cGVyIiBsZXMgdmFsZXVycyBkJ2V4cHJlc3Npb24gZXQgZGUgcsOpZHVpcmUgbCdpbXBhY3QgZGVzIHZhbGV1cnMgZXh0csOqbWVzIGRhbnMgbCdhbmFseXNlLiBDJ2VzdCBjZXBlbmRhbnQgdW5lIG5vcm1hbGlzYXRpb24gc2V1bGVtZW50ICppbnRyYS3DqWNoYW50aWxsb24qLgoKTGEgc3RhbmRhcmRpc2F0aW9uIHN1ciBsZSB0cm9pc2nDqG1lIHF1YXJ0aWxlIHBlcm1ldCB1bmUgbm9ybWFsaXNhdGlvbiAqaW50ZXItw6ljaGFudGlsbG9ucyouIExlcyBtw6lkaWFuZXMgZGUgdG91cyBsZXMgw6ljaGFudGlsbG9ucyBzb250IHByZXNxdWUgYWxpZ27DqWVzLiBDZWxhIHBlcm1ldCBub3RhbW1lbnQgZGUgY29uc2lkw6lyYWJsZW1lbnQgcsOpZHVpcmUgbCdpbXBhY3QgZGUgbGEgdGFpbGxlIGRlcyBsaWJyYWlyaWVzIHBvdXIgbCdhbmFseXNlIGVudHJlIMOpY2hhbnRpbGxvbnMuIFBhciBleGVtcGxlIGxlIGRheTJfMiBkb250IGwnZXhwcmVzc2lvbiBtb3llbm5lIHNlbWJsZSBuZXR0ZW1lbnQgcGx1cyBmYWlibGUsIHJldHJvdXZlIGRlcyB2YWxldXJzIHNpbWlsYWlyZSBhdXggYXV0cmVzIMOpY2hhbnRpbGxvbnMuCgoKIyMgNC4gQW5hbHlzZSBkZSByZWdyb3VwZW1lbnQgZGVzIGRvbm7DqWVzCgoqKipBIHZvdXMgZGUgam91ZXIhKioqCgojIyMgU8OpbGVjdGlvbiBkZSBnw6huZXMgZCdleHByZXNzaW9uIMOpbGV2w6llIGV0IHZhcmlhYmxlCgpQb3VyIHLDqWR1aXJlIGxlIG5vbWJyZSBkZSBnw6huZXMsIG5vdXMgYWxsb25zIMOpY2FydGVyIGxlcyBnw6huZXMgZmFpYmxlbWVudCBleHByaW3DqXMgKGxvZzIgbW95ZW4gaW5mw6lyaWV1ciDDoCA0KSwgZXQgbmUgcmV0ZW5pciBxdWUgY2V1eCBxdWkgbW9udHJlbnQgZGVzIHZhcmlhdGlvbnMgaW1wb3J0YW50ZXMgZW50cmUgw6ljaGFudGlsbG9ucy4gUG91ciBjZSBkZXJuaWVyIGNyaXTDqHJlLCBub3VzIG5vdXMgYmFzb25zIHN1ciBsYSB2YXJpYW5jZS4gCgpTw6lsZWN0aW9ubmV6IGxlcyBnw6huZXMgYXlhbnQgdW4gbml2ZWF1IGxvZzIgbW95ZW4gbWluaW1hbCBzdXDDqXJpZXVyIMOgIDUgKCRtID4gNSQpIGV0IHVuZSB2YXJpYW5jZSBzdXDDqXJpZXVyZSDDoCAyICgkc14yID4gMiQpLiBOb3RlOiBjZXMgdmFsZXVycyBzb250IHBhcmZhaXRlbWVudCBhcmJpdHJhaXJlcywgZWxsZXMgb250IMOpdMOpIGNob2lzaWVzIHBvdXIgb2J0ZW5pciB1biBub21icmUgcmFpc29ubmFibGUgZGUgZ8OobmVzLiAKCmBgYHtyIGdlbmVfc2VsZWN0aW9ufQojIyMjIFNlbGVjdGlvbiBvZiBhIHN1YnNldCBvZiBnZW5lcyB3aXRoIGlnaCBleHByZXNzaW9uIGFuZCB2YXJpYW5jZSAjIyMjCm1lc3NhZ2UoIlNlbGVjdGluZyBnZW5lcyB3aXRoIGhpZ2ggZXhwcmVzc2lvbiBhbmQgdmFyaWFuY2UiKQoKIyBTZWxlY3RpbmcgaGlnaCBleHByZXNzaW9uIGdlbmVzIGFuZCB3aXRoIGEgaGlnaCB2YXJpYW5jZQptb3N0X2V4cHJlc3NlZF9nZW5lc19zdGF0IDwtIGdlbmVfc3RhdF9ub3JtWyhnZW5lX3N0YXRfbm9ybSRtZWFuID4gNSkgJiAoZ2VuZV9zdGF0X25vcm0kdmFyID4gMiksXQoKIyBSZWNvdmVyaW5nIHRoZSBkZXNpcmVkIGdlbmVzIGZyb20gdGhlIG5vcm1hbGlzZWQgZGF0YQpmYV9leHByX2hpZ2hfZ2VuZXMgPC0gZmFfZXhwcl9sb2cyW21vc3RfZXhwcmVzc2VkX2dlbmVzX3N0YXQkZW5zZW1ibF9nZW5lX2lkLF0KCmBgYAoKRGVzc2luZXogZGVzIGhpc3RvZ3JhbW1lcyBkZXMgdmFsZXVycyBkJ2V4cHJlc3Npb24gYXZhbnQgZXQgYXByw6hzIGNldHRlIHPDqWxlY3Rpb24gZGUgZ8OobmVzLCBldCBjb21tZW50ZXogbGVzIGRpZmbDqXJlbmNlcy4gCgpgYGB7ciBoaXN0X2V4cHJfc2VsZWN0ZWRfZ2VuZXMsIGZpZy53aWR0aD04LCBmaWcuaGVpZ2h0PTYsIG91dC53aWR0aD0iMTAwJSIsIGZpZy5jYXA9IkRpc3RyaWJ1dGlvbiBvZiBub3JtYWxpc2VkIGV4cHJlc3Npb24gb2YgYWxsIGdlbmVzIHZlcnN1cyBzZWxlY3RlZCBoaWdobHkgZXhwcmVzc2VkIGdlbmVzLiJ9CiMjIyMgSGlzdG9ncmFtcyBvZiBleHByZXNzaW9uIGJlZm9yZSBhbmQgYWZ0ZXIgZ2VuZSBzZWxlY3Rpb24gIyMjIwoKcGFyKG1mcm93PWMoMSwgMikpCmhpc3QoZmFfZXhwcl9sb2cyLAogICAgIGJyZWFrcyA9IDEwMCwKICAgICB4bGltID0gYygwLCAyNSksCiAgICAgY2V4Lm1haW4gPSAwLjg1LAogICAgIGNleC5heGlzID0gMC43LAogICAgIGxhcyA9IDEsCiAgICAgY29sID0gInNreWJsdWUiLAogICAgIHhsYWIgPSAibm9ybWFsaXNlZCBjb3VudHMiLAogICAgIG1haW4gPSAiRGlzdHJpYnV0aW9uIG9mIGFsbCBnZW5lcyAKbm9ybWFsaXNlZCBjb3VudHMiKQoKaGlzdChmYV9leHByX2hpZ2hfZ2VuZXMsCiAgICAgYnJlYWtzID0gMTAwLAogICAgIHhsaW0gPSBjKDAsIDI1KSwKICAgICBjZXgubWFpbiA9IDAuODUsCiAgICAgY2V4LmF4aXMgPSAwLjcsCiAgICAgbGFzID0gMSwKICAgICBjb2wgPSAiZ29sZCIsCiAgICAgeGxhYiA9ICJub3JtYWxpc2VkIGNvdW50cyIsCiAgICAgbWFpbiA9ICJEaXN0cmlidXRpb24gb2YgaGlnaGx5IGV4cHJlc3NlZCBnZW5lcyAKbm9ybWFsaXNlZCBjb3VudHMiKQoKYGBgCgpPbiBhIGJlYXVjb3VwIHLDqWR1aXQgbGUgbm9tYnJlcyBkZSB2YWxldXJzIGQnZXhwcmVzc2lvbi4gSWwgcmVzdGUgY2VwZW5kYW50IGRlcyB2YWxldXJzIG51bGxlcyBlbiBwcm9wb3J0aW9uIGFzc2V6IMOpbGV2w6llLiBFbiBzZWxlY3Rpb25uYW50IHN1ciBsYSBiYXNlIGQndW5lIHZhcmlhbmNlIGZvcnRlLCBvbiBhIHNhbnMgZG91dGUgZ2FyZMOpIGRlcyBnw6huZXMgcXVpIG5lIHNvbnQgcGFzIGR1IHRvdXQgZXhwcmltw6lzIGRhbnMgY2VydGFpbmVzIGNvbmRpdGlvbnMsIGV0IGFjdGl2w6lzIGRhbnMgZCdhdXRyZXMsIHByb2JhYmxlbWVudCBlbiByw6lwb25zZSBhdSB0cmFpdGVtZW50IMOgIGwnYWNpZGUgZm9saXF1ZS4gCgpEZSBwbHVzIGxlIHNvbW1ldCBkdSBwaWMgZGUgZGlzdHJpYnV0aW9uIHN1ciBsZXMgZ8OobmVzIHPDqWxlY3Rpb25uw6lzIGVzdCBzaXR1w6kgw6AgdW5lIHZhbGV1ciBkZSBjb21wdGFnZSBub3JtYWxpc8OpZSBzaW1pbGFpcmUgw6AgY2VsbGUgcG91ciB0b3VzIGxlcyBnw6huZXMsIG1hbGdyw6kgbGUgY2hvaXggZGUgZ8OobmVzIGVuIG1veWVubmUgbGVzIHBsdXMgZXhwcmltw6lzLiBQZXV0LcOqdHJlIHBhcmNlIHF1J3VuZSBib25uZSBwYXJ0aWUgZGVzIGfDqG5lcyB0csOocyBleHByaW3DqXMgY29uc3RhbnRzIG9uIMOpdMOpIHJldGlyw6lzIHBhciBsZSBmaWx0cmUgc3VyIGxhIHZhcmlhbmNlLCBvbiBnYXJkZSBhaW5zaSBkZXMgZ8OobmVzIGF2ZWMgZGVzIHZhbGV1cnMgZCdleHByZXNzaW9uIGZhaWJsZXMgZGFucyBjZXJ0YWluZXMgY29uZGl0aW9ucyBxdWkgdmllbm5lbnQgImNvbXBlbnNlciIgbGVzIHZhbGV1cnMgZm9ydGVzIGRhbnMgZCdhdXRyZXMuCgpEZXNzaW5leiB1biBib3ggcGxvdCBwYXIgw6ljaGFudGlsbG9uIGRlcyB2YWxldXJzIGQnZXhwcmVzc2lvbiBhdmFudCBldCBhcHLDqHMgc8OpbGVjdGlvbiBkZXMgZ8OobmVzLCBldCBjb21tZW50ZXogbGUgcsOpc3VsdGF0LiAKCmBgYHtyIGJveHBsb3RzX2V4cHJfc2VsZWN0ZWRfZ2VuZXMsIGZpZy53aWR0aD0xMCwgZmlnLmhlaWdodD01LCBvdXQud2lkdGg9IjYwJSIsIGZpZy5jYXA9IkJveHBsb3RzIG9mIGFsbCBnZW5lcyB2ZXJzdXMgc2VsZWN0ZWQgZ2VuZXMgbm9ybWFsaXNlZCBleHByZXNzaW9uLiJ9CiMjIyMgQm94cGxvdHMgb2YgZXhwcmVzc2lvbiBiZWZvcmUgYW5kIGFmdGVyIGdlbmUgc2VsZWN0aW9uICMjIyMKCnBhcihtZnJvdz1jKDEsIDEpKQojIFdpdGggYWxsIGdlbmVzCmJveHBsb3QoZmFfZXhwcl9sb2cyLAogICAgICAgIG1haW4gPSAiU3RhbmRhcmRpc2VkIGFuZCBsb2cyIHRyYW5zZm9ybWVkIGV4cHJlc3Npb24KYWxsIGdlbmVzIiwKICAgICAgICBob3Jpem9udGFsID0gVFJVRSwKICAgICAgICBjb2wgPSBmYV9tZXRhJGNvbG9yLAogICAgICAgIGNleCA9IDAuNSwKICAgICAgICBjZXguYXhpcyA9IDAuNiwKICAgICAgICBsYXMgPSAxKQoKIyBXaXRoIHNlbGVjdGVkIGdlbmVzCmJveHBsb3QoZmFfZXhwcl9oaWdoX2dlbmVzLAogICAgICAgIG1haW4gPSAiU3RhbmRhcmRpc2VkIGFuZCBsb2cyIHRyYW5zZm9ybWVkIGV4cHJlc3Npb24Kc2VsZWN0ZWQgZ2VuZXMiLAogICAgICAgIGhvcml6b250YWwgPSBUUlVFLAogICAgICAgIGNvbCA9IGZhX21ldGEkY29sb3IsCiAgICAgICAgY2V4ID0gMC41LAogICAgICAgIGNleC5heGlzID0gMC42LAogICAgICAgIGxhcyA9IDEpCmBgYAoKTGVzIGRpZmbDqXJlbmNlcyBkZSBzdGF0aXN0aXF1ZXMgaW50ZXItw6ljaGFudGlsbG9ucyBzb250IMOgIG5vdXZlYXV4IHBsdXMgw6lsZXbDqWVzLCBsZXMgbcOpZGlhbmVzIHNvbnQgcGx1cyDDqWNhcnTDqWVzLiBMJ8OpY2hhbnRpbGxvbiBkYXkyXzIgZXN0IGVuY29yZSBhc3NleiBkaWZmw6lyZW50IGRlcyBhdXRyZXMsIGF2ZWMgZGVzIHZhbGV1cnMgZCdleHByZXNzaW9uIHBsdXMgw6l0YWzDqWVzLgoKIyMjIEFDUAoKRGVzc2luZXogdW4gcGxvdCBBQ1AgZGVzIMOpY2hhbnRpbGxvbnMgZW4gbGVzIGNvbG9yYW50IHBhciBjb25kaXRpb24gYXZhbnQgZXQgYXByw6hzIG5vcm1hbGlzYXRpb24uCgotIGF2ZWMgbGVzIGNvbXB0YWdlcyBicnV0cyBkZSBsYSBtYXRyaWNlIGQnZXhwcmVzc2lvbiBpbml0aWFsZSAoJGZhX2V4cHIkKQoKYGBge3IgYWNwX3Jhd19hbGxfZ2VuZXMsIGZpZy53aWR0aD04LCBmaWcuaGVpZ2h0PTgsIG91dC53aWR0aD0iNjAlIiwgZmlnLmNhcD0iUENBIG9uIHJhdyBkYXRhLCBhbGwgZ2VuZXMifQojIyMjIFBDQSBmcm9tIHJhdyBjb3VudHMsIGFsbCBnZW5lcyAjIyMjCm1lc3NhZ2UoIlJ1bm5pbmcgUENBIHdpdGggcmF3IGNvdW50cywgYWxsIGdlbmVzIikKCnJlc19wY2FfcmF3IDwtIFBDQSh0KGZhX2V4cHJfcmF3KSwgc2NhbGUudW5pdCA9IFRSVUUsIGdyYXBoID0gRkFMU0UpCgpmdml6X3BjYV9pbmQocmVzX3BjYV9yYXcsCiAgICAgICAgICAgICBjb2wuaW5kID0gZmFfbWV0YSRjb25kaXRpb24sCiAgICAgICAgICAgICBtZWFuLnBvaW50ID0gRkFMU0UsCiAgICAgICAgICAgICB0aXRsZSA9ICJQQ0Egb24gcmF3IGRhdGEsIGFsbCBnZW5lcyIpCgpgYGAKCi0gYXZlYyBsYSBtYXRyaWNlIGRlIHZhbGV1cnMgbm9ybWFsaXPDqWVzIGRlcyBnw6huZXMgZmlsdHLDqXMKCmBgYHtyIGFjcF9ub3JtX2ZpbHRlcmVkX2dlbmVzLCBmaWcud2lkdGg9OCwgZmlnLmhlaWdodD04LCBvdXQud2lkdGg9IjYwJSIsIGZpZy5jYXA9IlBDQSBvbiBub3JtYWxpc2VkIGRhdGEsIGFsbCBnZW5lcyJ9CiMjIyMgUENBIGZyb20gbm9ybWFsaXNlZCBjb3VudHMsIGFsbCBnZW5lcyAjIyMjCm1lc3NhZ2UoIlJ1bm5pbmcgUENBIHdpdGggbm9ybWFsaXNlZCBjb3VudHMsIGFsbCBnZW5lcyIpCgpyZXNfcGNhX2xvZzIgPC0gUENBKHQoZmFfZXhwcl9sb2cyKSwgc2NhbGUudW5pdCA9IFRSVUUsIGdyYXBoID0gRkFMU0UpCgpmdml6X3BjYV9pbmQocmVzX3BjYV9sb2cyLAogICAgICAgICAgICAgY29sLmluZCA9IGZhX21ldGEkY29uZGl0aW9uLAogICAgICAgICAgICAgbWVhbi5wb2ludCA9IEZBTFNFLAogICAgICAgICAgICAgdGl0bGUgPSAiUENBIG9uIG5vcm1hbGlzZWQgZGF0YSwgYWxsIGdlbmVzIikKCgpgYGAKCi0gYXZlYyBsYSBtYXRyaWNlIGZpbmFsZSAodHJhbnNmb3JtYXRpb24gbG9nMiwgZmlsdHJlIGRlcyBnw6huZXMgbm9uLWTDqXRlY3TDqXMsIHN0YW5kYXJkaXNhdGlvbiBldCBzw6lsZWN0aW9uIGRlcyBnw6huZXMgZm9ydGVtZW50IGV4cHJpbcOpcyBldCDDoCBoYXV0IGNvZWZmaWNpZW50IGRlIHZhcmlhdGlvbikKCmBgYHtyIGFjcF9ub3JtX3NlbGVjdGVkX2dlbmVzLCBmaWcud2lkdGg9OCwgZmlnLmhlaWdodD04LCBvdXQud2lkdGg9IjYwJSIsIGZpZy5jYXA9IlBDQSBvbiBub3JtYWxpc2VkIGRhdGEsIHNlbGVjdGVkIGdlbmVzIn0KIyMjIyBQQ0EgZnJvbSBub3JtYWxpc2VkIGNvdW50cywgc2VsZWN0ZWQgZ2VuZXMgIyMjIwptZXNzYWdlKCJSdW5uaW5nIFBDQSB3aXRoIG5vcm1hbGlzZWQgY291bnRzLCBzZWxlY3RlZCBnZW5lcyIpCgpyZXNfcGNhX2hpZ2hfZ2VuZXMgPC0gUENBKHQoZmFfZXhwcl9oaWdoX2dlbmVzKSwgc2NhbGUudW5pdCA9IFRSVUUsIGdyYXBoID0gRkFMU0UpCgpmdml6X3BjYV9pbmQocmVzX3BjYV9oaWdoX2dlbmVzLAogICAgICAgICAgICAgY29sLmluZCA9IGZhX21ldGEkY29uZGl0aW9uLAogICAgICAgICAgICAgbWVhbi5wb2ludCA9IEZBTFNFLAogICAgICAgICAgICAgdGl0bGUgPSAiUENBIG9uIG5vcm1hbGlzZWQgZGF0YSwgc2VsZWN0ZWQgZ2VuZXMiKQoKYGBgCgpMYSBub3JtYWxpc2F0aW9uIGRlcyBkb25uw6llcyBwZXJtZXQgZGUgcmFwcHJvY2hlciBjb25zaWTDqXJhYmxlbWVudCBsJ8OpY2hhbnRpbGxvbiBub3JtYWxfMiBldCBkYXkxXzEgcXVpIHNlbWJsYWllbnQgw6p0cmUgZGVzIG91dGxpZXJzLCBkZSBsZXVycyByw6lwbGljYXRzIGJpb2xvZ2lxdWVzLiBFbGxlIHBlcm1ldCDDqWdhbGVtZW50IGRlIHJhc3NlbWJsZXIgbGVzIMOpY2hhbnRpbGxvbnMgZGF5Ml8xIGV0IGRheTJfMy4gTGEgbm9ybWFsaXNhdGlvbiBwZXJtZXQgw6lnYWxlbWVudCBkZSBtaWV1eCBzw6lwYXJlciBsZXMgY29uZGl0aW9ucyBkYXkzIGV0IGRheTcuCgpkYXkyXzIgc2VtYmxlIMOqdHJlIHVuIG91dGxpZXIsIHF1ZWwgcXVlIHNvaXQgbGUgdHJhaXRlbWVudCBkZXMgZG9ubsOpZXMuClNpIG9uIGwnZXhjbHV0LCBvbiBvYnNlcnZlIGRhbnMgbCdBQ1Agc3VyIGxlcyBnw6huZXMgc8OpbGVjdGlvbm7DqXMgdW5lIGNsYWlyZSBzw6lwYXJhdGlvbiBsZSBsb25nIGRlIGxhIGRpbWVuc2lvbiAyIGVudHJlIGxlcyDDqWNoYW50aWxsb25zIGFwcsOocyB0cmFpdGVtZW50IMOgIGwnYWNpZGUgZm9saXF1ZSBldCBsZXMgw6ljaGFudGlsbG9ucyBub3JtYXV4LiBPbiBvYnNlcnZlIMOpZ2FsZW1lbnQgcXVlIGxlcyBjb25kaXRpb25zIGRheTE0ICJyZWRlc2NlbmRlbnQiIGxlIGxvbmcgZGUgY2V0dGUgZGltZW5zaW9uLCBwcm9iYWJsZW1lbnQgY2FyIGwnZWZmZXQgZGUgbCdpbmplY3Rpb24gc2UgcsOpc29yYmUuCk9uIHBldXQgbcOqbWUgb2JzZXJ2ZXIgdW5lIGRpc3Bvc2l0aW9uIGN5Y2xpcXVlIGhvcmFpcmUgcGFydGFudCBkZSBub3JtYWwsIHBhc3NhbnQgYXV4IHRlbXBzIHLDqWNlbnRzIGFwcsOocyBsJ2luamVjdGlvbiBwdWlzIHRlbmRhbnQgw6AgcmV0b3VybmVyIHZlcnMgbGVzIGNvbmRpdGlvbnMgbm9ybWFsZXMuCgojIyMgQ2x1c3RlcmluZwoKLSBDYWxjdWxleiBsZXMgbWF0cmljZXMgZGUgZGlzdGFuY2UgZW50cmUgw6ljaGFudGlsbG9ucywgZW4gdXRpbGlzYW50IHJlc3BlY3RpdmVtZW50IGxlcyBkaXN0YW5jZXMgZXVjbGlkaWVubmUgKGBkaXN0KClgKSwgY29lZmZpY2llbnQgZGUgUGVhcnNvbiAoYGNvcigsIG1ldGhvZCA9ICJwZWFyc29uIilgKSAgZXQgZGUgU3BlYXJtYW4gKGBjb3IoLCBtZXRob2QgPSAic3BlYXJtYW4iKWApLgoKCmBgYHtyIHNhbXBsZV9kaXN0YW5jZXN9CiMjIyMgU2FtcGxlIGRpc3RhbmNlcyAjIyMjCm1lc3NhZ2UoIkNvbXB1dGluZyBpbnRlci1zYW1wbGUgZGlzdGFuY2VzIikKCiMjIEV1Y2xpZGlhbgojIE9uIHRyYW5zcG9zZSBsYSBtYXRyaWNlIGQnZXhwcmVzc2lvbiBwb3VyIGNhbGN1bGVyIGxhIGRpc3RhbmNlIGVudHJlIMOpY2hhbnRpbGxvbnMuCmRpc3RfZXVjbGlkaWFuIDwtIGRpc3QodChmYV9leHByX2hpZ2hfZ2VuZXMpLCBtZXRob2QgPSAiZXVjbGlkZWFuIikKCgojIExlcyBkaXN0YW5jZXMgZGUgUGVhcnNvbiBldCBTcGVhcm1hbiBzb250IGNhbGN1bMOpZXMgZW4gc291c3RyYXlhbnQgw6AgMSBsYSBjb3Jyw6lsYXRpb24gZGUgUGVhcnNvbiBvdSBTcGVhcm1hbgoKIyMgUGVhcnNvbgpkaXN0X3BlYXJzb24gPC0gYXMuZGlzdCgxIC0gY29yKGZhX2V4cHJfaGlnaF9nZW5lcywgdXNlID0gImV2ZXJ5dGhpbmciLCAKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICBtZXRob2QgPSAicGVhcnNvbiIpKQoKIyMgU3BlYXJtYW4KZGlzdF9zcGVhcm1hbiA8LSBhcy5kaXN0KDEgLSBjb3IoZmFfZXhwcl9oaWdoX2dlbmVzLCB1c2UgPSAiZXZlcnl0aGluZyIsIAogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICBtZXRob2QgPSAic3BlYXJtYW4iKSkKCmBgYAoKLSBFZmZlY3R1ZXogdW4gY2x1c3RlcmluZyBoacOpcmFyY2hpcXVlIGRlcyDDqWNoYW50aWxsb25zLCBlbiB1dGlsaXNhbnQgbGUgY3JpdMOocmUgYGNvbXBsZXRlYCBwb3VyIGwnYWdnbG9tw6lyYXRpb24uIENvbXBhcmV6IGxlcyBhcmJyZXMgZCfDqWNoYW50aWxsb25zIG9idGVudXMgYXZlYyBjZXMgdHJvaXMgbcOpdHJpcXVlcyBldCBjaG9pc2lzc2V6IGNlbGxlIHF1aSB2b3VzIHBhcmHDrnQgbGEgcGx1cyBwZXJ0aW5lbnRlLgoKYGBge3Igc2FtcGxlX2NsdXN0ZXJpbmcsIGZpZy53aWR0aD0xMiwgcGxvdC5oZWlnaHQ9NSwgb3V0LndpZHRoPSIxMDAlIiwgZmlnLmNhcD0iRGVuZG9ncmFtcyBzYW1wbGVzIGNsdXN0ZXJpbmcuIn0KIyMjIyBTYW1wbGUgY2x1c3RlcmluZyAjIyMjCm1lc3NhZ2UoIlNhbXBsZSBjbHVzdGVyaW5nIikKCnRyZWVfZXVjbGlkaWFuIDwtIGhjbHVzdChkaXN0X2V1Y2xpZGlhbiwgbWV0aG9kID0gImNvbXBsZXRlIikKdHJlZV9wZWFyc29uIDwtIGhjbHVzdChkaXN0X3BlYXJzb24sIG1ldGhvZCA9ICJjb21wbGV0ZSIpCnRyZWVfc3BlYXJtYW4gPC0gaGNsdXN0KGRpc3Rfc3BlYXJtYW4sIG1ldGhvZCA9ICJjb21wbGV0ZSIpCgpwYXIoYmcgPSAiZGFya2dyZXkiLCBtZnJvdz1jKDEsIDEpKQpwbG90Q29sb3JlZENsdXN0ZXJzKHRyZWVfZXVjbGlkaWFuLCBsYWJzID0gZmFfbWV0YSRzYW1wbGVOYW1lLAogICAgICAgICAgICAgICAgICAgIHlsYWIgPSBOQSwgeGxhYiA9IE5BLCBjZXggPSAxLCBsYXMgPSAxLAogICAgICAgICAgICAgICAgICAgIGNvbHMgPSBmYV9tZXRhJGNvbG9yLCBjb2wgPSAid2hpdGUiLAogICAgICAgICAgICAgICAgICAgIG1haW4gPSAiU2FtcGxlcyBFdWNsaWRpYW4gZGlzdGFuY2UgaGllcmFyY2hpY2FsIGNsdXN0ZXJpbmcsCmNvbXBsZXRlIGxpbmthZ2UiKQoKCnBsb3RDb2xvcmVkQ2x1c3RlcnModHJlZV9wZWFyc29uLCBsYWJzID0gZmFfbWV0YSRzYW1wbGVOYW1lLAogICAgICAgICAgICAgICAgICAgIHlsYWIgPSBOQSwgeGxhYiA9IE5BLCBjZXggPSAxLCBsYXMgPSAxLAogICAgICAgICAgICAgICAgICAgIGNvbHMgPSBmYV9tZXRhJGNvbG9yLCBjb2wgPSAid2hpdGUiLAogICAgICAgICAgICAgICAgICAgIG1haW4gPSAiU2FtcGxlcyBQZWFyc29uIGRpc3RhbmNlIGhpZXJhcmNoaWNhbCBjbHVzdGVyaW5nLApjb21wbGV0ZSBsaW5rYWdlIikKCnBsb3RDb2xvcmVkQ2x1c3RlcnModHJlZV9zcGVhcm1hbiwgbGFicyA9IGZhX21ldGEkc2FtcGxlTmFtZSwKICAgICAgICAgICAgICAgICAgICB5bGFiID0gTkEsIHhsYWIgPSBOQSwgY2V4ID0gMSwgbGFzID0gMSwKICAgICAgICAgICAgICAgICAgICBjb2xzID0gZmFfbWV0YSRjb2xvciwgY29sID0gIndoaXRlIiwKICAgICAgICAgICAgICAgICAgICBtYWluID0gIlNhbXBsZXMgU3BlYXJtYW4gZGlzdGFuY2UgaGllcmFyY2hpY2FsIGNsdXN0ZXJpbmcsCmNvbXBsZXRlIGxpbmthZ2UiKQoKYGBgCgoKTGUgY2x1c3RlcmluZyBiYXPDqSBzdXIgbGEgZGlzdGFuY2UgZXVjbGlkaWVubmUgc8OpcGFyZSBjb21wbMOodGVtZW50IGwnw6ljaGFudGlsbG9uIGRheTJfMiBkZXMgYXV0cmVzLCBpbCBlc3QgdHJhaXTDqSBjb21tZSB1biBvdXRsaWVyIHRyw6hzIG1hcnF1w6kuIExlcyDDqWNoYW50aWxsb25zIG5vcm1hdXggc29udCBncm91cMOpcyBlbnRyZSBldXggZXQgc8OpcGFyw6kgZCd1biBjbHVzdGVyIHF1aSBlbmdsb2JlIHRvdXMgbGVzIMOpY2hhbnRpbGxvbnMgdHJhaXTDqXMgw6AgcGFydCBsZSBkYXkyXzIuCkNlIGNsdXN0ZXIgZCfDqWNoYW50aWxsb25zIHRyYWl0w6lzIGVzdCBzw6lwYXLDqSBlbiB1biBncm91cGUgZGUgcHLDqWzDqHZlbWVudHMgcGV1IGFwcsOocyBsZSB0cmFpdGVtZW50IMOgIGwnYWNpZGUgZm9saXF1ZSAoZGF5MSwgZGF5MiwgZGF5MykgZXQgdW4gYXV0cmUgYXZlYyBkZXMgdGVtcHMgcGx1cyBsb25ncyAoZGF5NyBldCBkYXkxNCkuCgpMZXMgY2x1c3RlcmluZ3MgYmFzw6lzIHN1ciBsZXMgZGlzdGFuY2VzIGRlIFNwZWFybWFuIGV0IFBlYXJzb24gZG9ubmVudCBkZXMgcsOpc3VsdGF0cyB0csOocyBwcm9jaGVzLiBEZXV4IHN1cGVyIGNsdXN0ZXJzIGRvbnQgdW4gY29tcHJlbmFudCBsZXMgw6ljaGFudGlsbG9ucyBwcsOpbGV2w6lzIHBldSBhcHLDqHMgbGUgdHJhaXRlbWVudCwgw6AgbCdpbnTDqXJpZXVyIGR1cXVlbCBsZXMgZGlmZsOpcmVudGVzIHRlbXBzIHNvbnQgYmllbiByZWdyb3Vww6lzIChtaXMgw6AgcGFydCBsZSBkYXkyXzIpLCBldCB1biByZWdyb3VwYW50IGxlcyBjb25kaXRpb25zIHNhbnMgdHJhaXRlbWVudCBldCBsZXMgcHLDqWzDqHZlbWVudCBwbHVzIHRhcmRpZnMuCgpMZXMgdHJvaXMgY2x1c3RlcmluZ3MgZG9ubmVudCBkZXMgcsOpc3VsdGF0cyBxdWkgbWUgc2VtYmxlbnQgY29ow6lyZW50cywgY2VwZW5kYW50IGwnZXhjdXNpb24gbmV0dGUgZHUgZGF5Ml8yIGRlcyBhdXRyZXMgY29uZGl0aW9ucyBwYXIgbGEgZGlzdGFuY2UgZXVjbGlkaWVubmUgbWUgcGFyYWl0IGp1c3RpZmnDqWUgKGF1IHJlZ2FyZCBkZSBsYSBQQ0EpIGFpbnNpIHF1ZSBsYSBzw6lwYXJhdGlvbiBwbHVzIGZyYW5jaGUgZGVzIGNvbmRpdGlvbnMgbm9ybWFsZXMgZXQgZGVzIGNvbmRpdGlvbnMgdHJhaXTDqWVzLgoKSmUgZMOpY2lkZSBkJ3V0aWxpc2VyIGxlIGNsdXN0ZXJpbmcgaGllcmFyY2hpcXVlIGRlcyDDqWNoYW50aWxsb25zIGJhc8OpIHN1ciBsYSBkaXN0YW5jZSBldWNsaWRpZW5uZS4KCi0gRWZmZWN0dWV6IHVuIGNsdXN0ZXJpbmcgaGnDqXJhcmNoaXF1ZSBkZXMgZ8OobmVzIGVuIHV0aWxpc2FudCBsYSBkaXN0YW5jZSBiYXPDqWUgc3VyIGxlIGNvZWZmaWNpZW50IGRlIFBlYXJzb24gZXQgbGEgcsOoZ2xlIGQnYWdnbG9tw6lyYXRpb24gY29tcGzDqHRlCgpgYGB7ciBnZW5lX3RyZWV9CiMjIyMgR2VuZSB0cmVlIHdpdGggUGVhcnNvbiBjb3JyZWxhdGlvbiAjIyMjCm1lc3NhZ2UoIkRyYXdpbmcgZ2VuZSB0cmVlIikKCiMgUGVhcnNvbiBkaXN0YW5jZSBnZW5lCmRpc3RfZ2VuZV9wZWFyc29uIDwtIGFzLmRpc3QoMSAtIGNvcih0KGZhX2V4cHJfaGlnaF9nZW5lcyksIAogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIHVzZSA9ICJldmVyeXRoaW5nIiwgbWV0aG9kID0gInBlYXJzb24iKSkKIyB0cmVlCnRyZWVfZ2VuZV9wZWFyc29uIDwtIGhjbHVzdChkaXN0X2dlbmVfcGVhcnNvbiwgbWV0aG9kID0gImNvbXBsZXRlIikKYGBgCgotIERlc3NpbmV6IHVuIGFyYnJlIGF2ZWMgbGUgcsOpc3VsdGF0IGR1IGNsdXN0ZXJpbmcgZGVzIGfDqG5lcyBldCBjb21tZW50ZXogc2Egc3RydWN0dXJlLiBTaSB2b3VzIGRldmlleiBjaG9pc2lyIGRlIGZhw6dvbiBhcmJpdHJhaXJlIHVuIG5vbWJyZSBkZSBjbHVzdGVycywgcXVlIGNob2lzaXJpZXotdm91cyA/IFBvdXJxdW9pID8gUGFzIGRlIHBhbmlxdWUsIG5vdXMgcG91dm9ucyBhc3N1bWVyIGljaSBxdWUgbGEgcsOpcG9uc2UgY29tcG9ydGUgdW5lIHBhcnQgZGUgc3ViamVjdGl2aXTDqS4gCgpgYGB7ciBnZW5lX3RyZWVfd2l0aF9ib3hlc30KIyMjIyBQbG90IHRoZSBnZW5lIHRyZWUgd2l0aCBib3hlcyB0byBkZW5vdGUgdGhlIGNsdXN0ZXJzICMjIyMKbWVzc2FnZSgiUGxvdHRpbmcgZ2VuZSB0cmVlIikKCnBhcihiZyA9ICJ3aGl0ZSIpCnBsb3QodHJlZV9nZW5lX3BlYXJzb24sIGxhcyA9IDEsIAogICAgIHlsYWIgPSBOQSwgeGxhYiA9IE5BLCBjZXggPSAwLjEsIGhhbmcgPSAtMSwgY29sID0gImdyZXkiLAogICAgIG1haW4gPSAiR2VuZXMgUGVhcnNvbiBkaXN0YW5jZSBoaWVyYXJjaGljYWwgY2x1c3RlcmluZywKY29tcGxldGUgbGlua2FnZSIpCgojIEVzdGltYXRpbmcgYW4gYXBwcm9wcmlhdGUgbnVtYmVyIG9mIGNsdXN0ZXJzCmFibGluZShoPTEuNywgY29sPSAicmVkIikKCgojIExldCdzIHRyeSBjdXR0aW5nIHRoZSB0cmVlIGludG8gNiBjbHVzdGVycyBhdCB0aGUgaGVpZ2h0IDEuNwoKY2x1c3RlcnNfZ2VuZV9wZWFyc29uIDwtIGN1dHJlZSh0cmVlX2dlbmVfcGVhcnNvbiwgaD0xLjcpCgojIEhvdyBtYW55IGdlbmVzIGFyZSBpbiBlYWNoIGNsdXN0ZXI/CnRhYmxlKGNsdXN0ZXJzX2dlbmVfcGVhcnNvbikKCiMgU2F2aW5nIGNsdXN0ZXJpbmcgcmVzdWx0cyBpbiBnZW5lIGRhdGFmcmFtZSAKbW9zdF9leHByZXNzZWRfZ2VuZXNfc3RhdCRjbHVzdGVyIDwtIGNsdXN0ZXJzX2dlbmVfcGVhcnNvbgoKCiMgV2UgYWRkIHRoZSBjbHVzdGVyIGluZm9ybWF0aW9uIGluIHRoZSBtb3N0X2V4cHJlc3NlZF9nZW5lc19zdGF0Cm15X2NvbG9ycyA9IGMoIm9yYW5nZSIsICJibHVlIiwgImRhcmtyZWQiLCAKICAgICAgICAgICAgICAic2t5Ymx1ZSIsICJkYXJrZ3JlZW4iKQoKcGxvdCh0cmVlX2dlbmVfcGVhcnNvbiwgbGFzID0gMSwgCiAgICAgeWxhYiA9IE5BLCB4bGFiID0gTkEsIGNleCA9IDAuMSwgaGFuZyA9IC0xLCBjb2wgPSAiZ3JleSIsCiAgICAgbWFpbiA9ICJHZW5lcyBQZWFyc29uIGRpc3RhbmNlIGhpZXJhcmNoaWNhbCBjbHVzdGVyaW5nLApjb21wbGV0ZSBsaW5rYWdlIikKCiAgcmVjdC5oY2x1c3QodHJlZV9nZW5lX3BlYXJzb24sIGg9MS43LCBib3JkZXIgPSBteV9jb2xvcnMpCgpgYGAKCgpMJ2FyYnJlIGNvbXBvcnRlIHVuZSBzw6lwYXJhdGlvbiBlbiBkZXV4IGNsdXRlcnMgcHJpbmNpcGF1eCBxdWkgc2Ugc3ViZGl2aXNlbnQgZW5zdWl0ZSByYXBpZGVtZW50LiBMZSBjaG9peCBkdSBub21icmUgZGUgY2x1c3RlcnMgbidlc3QgcGFzIMOpdmlkZW50LCB1bmUgc8OpcGFyYXRpb24gZW4gNSBvdSA2IGNsdXN0ZXJzIG1lIHBhcmFpdHJhaXQgcmFpc29ubmFibGUuIEVuIGNvdXBhbnQgbCdhcmJyZSDDoCB1bmUgaGF1dGV1ciBkZSAxLjcsIGonb3B0ZSBwb3VyIDUgY2x1c3RlcnMuIAoKCi0gRGVzc2luZXogdW5lIGhlYXRtYXAgZHUgcsOpc3VsdGF0LCBlbiBzw6lsZWN0aW9ubmFudCBsZXMgZGV1eCByw6lzdWx0YXRzIGRlIGNsdXN0ZXJpbmcgY2ktZGVzc3VzIHBvdXIgbGVzIGfDqG5lcyBldCBsZXMgw6ljaGFudGlsbG9ucy4gCgpgYGB7ciBoZWF0bWFwX2JpY2x1c3RlcmluZywgZmlnLndpZHRoPTEwLCBmaWcuaGVpZ2h0PTgsIG91dC53aWR0aD0iOTAlIiwgZmlnLmNhcD0iQmljbHVzdGVyaW5nIG9mIGdlbmVzIGFuZCBzYW1wbGVzLiJ9CiMjIyMgSGVhdG1hcCB3aXRoIGJpY2x1c3RlcmluZyAjIyMjCm1lc3NhZ2UoImhlYXRtYXAgd2l0aCBiaWNsdXN0ZXJpbmciKQoKYW5ub3RfY2x1c3QgPSBkYXRhLmZyYW1lKGFzLmZhY3Rvcihtb3N0X2V4cHJlc3NlZF9nZW5lc19zdGF0JGNsdXN0ZXIpKQpjb2xuYW1lcyhhbm5vdF9jbHVzdCkgPC0gImNsdXN0ZXIiCnJvd25hbWVzKGFubm90X2NsdXN0KSA8LSByb3duYW1lcyhmYV9leHByX2hpZ2hfZ2VuZXMpCgpwaGVhdG1hcChmYV9leHByX2hpZ2hfZ2VuZXMsCiAgICAgICAgIGNsdXN0ZXJfcm93cyA9IFRSVUUsCiAgICAgICAgIGNsdXN0ZXJfY29scyA9IFRSVUUsCiAgICAgICAgIGNsdXN0ZXJpbmdfZGlzdGFuY2Vfcm93cyA9IGRpc3RfZ2VuZV9wZWFyc29uLAogICAgICAgICBjbHVzdGVyaW5nX2Rpc3RhbmNlX2NvbHMgPSBkaXN0X2V1Y2xpZGlhbiwKICAgICAgICAgYm9yZGVyX2NvbG9yID0gTkEsCiAgICAgICAgIHNob3dfcm93bmFtZXMgPSBGQUxTRSwKICAgICAgICAgYW5ub3RhdGlvbl9yb3cgPSBhbm5vdF9jbHVzdCwKICAgICAgICAgYW5ub3RhdGlvbl9uYW1lc19yb3cgPSBGQUxTRSwKICAgICAgICAgY3V0cmVlX3Jvd3MgPSA1LAogICAgICAgICBjdXRyZWVfY29scyA9IDUsCiAgICAgICAgIHNjYWxlID0gInJvdyIsCiAgICAgICAgIHVzZV9yYXN0ZXIgPSBUUlVFLAogICAgICAgICBhbmdsZV9jb2wgPSAiNDUiLAogICAgICAgICBtYWluID0gIkJpY2x1c3RlcmluZyBvZiBnZW5lcyAoUGVhcnNvbiBkaXN0YW5jZSkgCmFuZCBzYW1wbGVzIChldWNsaWRpYW4gZGlzdGFuY2UpIikKCmBgYAoKLSBEZXNzaW5leiB1bmUgaGVhdG1hcCBkdSByw6lzdWx0YXQsIGVuIGFmZmljaGFudCB1biBhcmJyZSBzdXIgbGVzIGfDqG5lcyBtYWlzIHBhcyBzdXIgbGVzIMOpY2hhbnRpbGxvbnMKCmBgYHtyIGhlYXRtYXBfZ2VuZV9jbHVzdGVyaW5nLCBmaWcud2lkdGg9MTAsIGZpZy5oZWlnaHQ9OCwgb3V0LndpZHRoPSI5MCUiLCBmaWcuY2FwPSJIZWF0bWFwIHdpdGggY2x1c3RlcmluZyBvZiBnZW5lcyAoUGVhcnNvbiBkaXN0YW5jZSkifQoKIyMjIyBIZWF0bWFwIHdpdGggY2x1c3RlcmluZyBvbiBnZW5lcyBvbmx5ICMjIyMKbWVzc2FnZSgiSGVhdG1hcCB3aXRoIGdlbmUgY2x1c3RlcmluZyIpCgpwaGVhdG1hcChmYV9leHByX2hpZ2hfZ2VuZXMsCiAgICAgICAgIGNsdXN0ZXJfcm93cyA9IFRSVUUsCiAgICAgICAgIGNsdXN0ZXJfY29scyA9IEZBTFNFLAogICAgICAgICBjbHVzdGVyaW5nX2Rpc3RhbmNlX3Jvd3MgPSBkaXN0X2dlbmVfcGVhcnNvbiwKICAgICAgICAgYm9yZGVyX2NvbG9yID0gTkEsCiAgICAgICAgIHNob3dfcm93bmFtZXMgPSBGQUxTRSwKICAgICAgICAgbGVnZW5kID0gRkFMU0UsCiAgICAgICAgIGFubm90YXRpb25fcm93ID0gYW5ub3RfY2x1c3QsCiAgICAgICAgIGFubm90YXRpb25fbmFtZXNfcm93ID0gRkFMU0UsCiAgICAgICAgIGN1dHJlZV9yb3dzID0gNSwKICAgICAgICAgc2NhbGUgPSAicm93IiwKICAgICAgICAgdXNlX3Jhc3RlciA9IFRSVUUsCiAgICAgICAgIGFuZ2xlX2NvbCA9ICI0NSIsCiAgICAgICAgIG1haW4gPSAiSGVhdG1hcCB3aXRoIGNsdXN0ZXJpbmcgb2YgZ2VuZXMgb25seSAKKFBlYXJzb24gZGlzdGFuY2UpIikKCmBgYAoKSW50ZXJwcsOpdGV6IGxlcyByw6lzdWx0YXRzIGVuIHF1ZWxxdWVzIHBocmFzZXMuIAoKT24gb2JzZXJ2ZSBhdmVjIG91IHNhbnMgY2x1c3RlcmluZyBkZXMgw6ljaGFudGlsbG9ucyBsZSBwcm9maWwgYXR5cGlxdWUgZGUgbCfDqWNoYW50aWxsb24gZGF5Ml8yLiDDgCBtb24gc2VucywgY2VsYSBjb25maXJtZSBsZSBjaG9peCBkZSBsYSBkaXN0YW5jZSBldWNsaWRpZW5uZSBwb3VyIGxlIGNsdXN0ZXJpbmcgZGVzIMOpY2hhbnRpbGxvbnMuIENldCDDqWNoYW50aWxsb24gc3VyZXhwcmltZSBuZXR0ZW1lbnQgbGUgY2x1c3RlciBkZSBnw6huZXMgMyBldCBzb3VzLWV4cHJpbWUgbGUgY2x1c3RlciAxLgoKRW5zdWl0ZSwgb24gb2JzZXJ2ZSBxdWUgbGUgY2x1c3RlciBkZSBnw6huZXMgMiBlc3Qgc291cy1leHByaW3DqSBkYW5zIGxlcyBjb25kaW9ucyBub3JtYWxlcyBwYXIgcmFwcG9ydCBhdXggY29uZGl0aW9ucyB0cmFpdMOpZXMuIENlcyBnw6huZXMgc29udCBwb3NzaWJsZW1lbnQgaW1wbGlxdcOpcyBkYW5zIGxhIHLDqXBvbnNlIGF1IHRyYWl0ZW1lbnQgw6AgbCdhY2lkZSBmb2xpcXVlIGV0IMOgIGwnYXR0ZWludGUgYXV4IHJlaW5zIHF1J2lsIGNhdXNlLiBMZSBuaXZlYXUgZCdleHByZXNzaW9uIGR1IGNsdXN0ZXIgMiByZWRlc2NlbmQgZGFucyBsZXMgZGF5MTQuCgpMZXMgY2x1c3RlcnMgZGUgZ8OobmVzIDEgZXQgNCBzb250IGdsb2JhbGVtZW50IG1vaW5zIGV4cHJpbcOpcyBkYW5zIGxlcyBjb25kaW9ucyBwZXUgYXByw6hzIGxlIHRyYWl0ZW1lbnQuIExldXJzIG5pdmVhdXggZCdleHByZXNzaW9uIHJlbW9udGVudCBkYW5zIGxlcyBjb25kaXRpb25zIHBsdXMgdGFyZGl2ZXMuCgojIyA1LiBFbnJpY2hpc3NlbWVudCBmb25jdGlvbm5lbAoKKioqQSB2b3VzIGRlIGpvdWVyISoqKgoKRWZmZWN0dWV6IHVuZSBhbmFseXNlIGQnZW5yaWNoaXNzZW1lbnQgZm9uY3Rpb25uZWwgYXZlYyBsZXMgcHJpbmNpcGF1eCBjbHVzdGVycyBvYnRlbnVzIGRhbnMgbGEgc2VjdGlvbiBwcsOpY8OpZGVudGUuIAoKYGBge3IgZnVuY3Rpb25hbF9lbnJpY2htZW50LCBmaWcud2lkdGg9NywgZmlnLmhlaWdodD01LCBvdXQud2lkdGg9IjgwJSIsIGZpZy5jYXA9IkZvbmN0aW9uYWwgZW5yaWNobWVudCBhbmFseXNpcyJ9CgojIyMjIFJ1biBlbnJpY2htZW50IGFuYWx5c2lzIHdpdGggZ29zdCgpICMjIyMKbWVzc2FnZSgiUnVubmluZyBlbnJpY2htZW50IGFuYWx5c2lzIGZvciB0aGUgc2VsZWN0ZWQgZ2VuZXMiKQoKZ2VuZV9saXN0IDwtIG1vc3RfZXhwcmVzc2VkX2dlbmVzX3N0YXQkZW5zZW1ibF9nZW5lX2lkCmdvc3RfcmVzIDwtIGdvc3QocXVlcnkgPSBnZW5lX2xpc3QsCiAgICAgICAgICAgICAgICAgb3JnYW5pc20gPSAibW11c2N1bHVzIiwKICAgICAgICAgICAgICAgICBzaWduaWZpY2FudCA9IFRSVUUsCiAgICAgICAgICAgICAgICAgdXNlcl90aHJlc2hvbGQgPSAwLjA1LCAjIGN1c3RvbSBwLXZhbHVlIHRocmVzaG9sZCBmb3Igc2lnbmlmaWNhbmNlCiAgICAgICAgICAgICAgICAgY29ycmVjdGlvbl9tZXRob2QgPSAiZmRyIiwKICAgICAgICAgICAgICAgICBkb21haW5fc2NvcGUgPSAiYW5ub3RhdGVkIiwKICAgICAgICAgICAgICAgICBhc19zaG9ydF9saW5rID0gRkFMU0UpCgpnb3N0cGxvdChnb3N0X3JlcywgY2FwcGVkID0gVFJVRSwgCiAgICAgICAgIGludGVyYWN0aXZlID0gVFJVRSkKCmBgYAoKSmUgY29tcHJlbmRzIG1hbCBsYSBkaWZmw6lyZW5jZSBlbnRyZSBsZXMgw6lub25jw6lzIGRlIGxhIHF1ZXN0aW9uIDUgZXQgZHUgY2hhbGxlbmdlIGZhY3VsdGF0aWYuIEplIHN1cHBvc2UgcXVlIGRhbnMgbGEgcXVlc3Rpb24gb2JsaWdhdG9pcmUgaWwgZXN0IGRlbWFuZMOpIHVuZSBhbmFseXNlIGQnZW5yaWNoaXNzZW1lbnQgZ2xvYmFsZSBzdXIgdG91cyBsZXMgZ8OobmVzIGxlcyBwbHVzIGV4cHJpbcOpcyBldCB2YXJpYWJsZXMgZXQgcXVlIGxlcyBjbHVzdGVycyBkZSBnw6huZXMgbidpbnRlcnZpZW5uZW50IHF1ZSBkYW5zIGxhIHNlY3Rpb24gZmFjdWx0YXRpdmUuLi4KCioqQ2hhbGxlbmdlIGZhbGN1bHRhdGlmOioqCgpFZmZlY3R1ZXogdW5lIGFuYWx5c2UgZCdlbnJpY2hpc3NlbWVudCBzdXIgY2hhY3VuIGRlcyBjbHVzdGVycyBvYnRlbnVzIMOgIHBhcnRpciBkZSBsJ2FyYnJlIGRlcyBnw6huZXMuIAoKYGBge3IgZnVuY3Rpb25hbF9lbnJpY2htZW50X2NsdXN0ZXJzLCBmaWcud2lkdGg9NywgZmlnLmhlaWdodD0xNiwgb3V0LndpZHRoPSI4MCUiLCBmaWcuY2FwPSJGb25jdGlvbmFsIGVucmljaG1lbnQgYW5hbHlzaXMsIGJ5IGNsdXN0ZXIifQojIyMjIEVucmljaG1lbnQgYnkgY2x1c3RlciAjIyMjCm1lc3NhZ2UoIkVucmljaG1lbnQgYW5hbHlzaXMgYnkgY2x1c3RlcnMiKQoKIyMgRXh0cmFjdGluZyBsaXN0IG9mIGdlbmVzIGJ5IGNsdXN0ZXIKCmNsdXN0ZXIxIDwtIG1vc3RfZXhwcmVzc2VkX2dlbmVzX3N0YXRbbW9zdF9leHByZXNzZWRfZ2VuZXNfc3RhdCRjbHVzdGVyID09IDEsICJlbnNlbWJsX2dlbmVfaWQiXQpjbHVzdGVyMiA8LSBtb3N0X2V4cHJlc3NlZF9nZW5lc19zdGF0W21vc3RfZXhwcmVzc2VkX2dlbmVzX3N0YXQkY2x1c3RlciA9PSAyLCAiZW5zZW1ibF9nZW5lX2lkIl0KY2x1c3RlcjMgPC0gbW9zdF9leHByZXNzZWRfZ2VuZXNfc3RhdFttb3N0X2V4cHJlc3NlZF9nZW5lc19zdGF0JGNsdXN0ZXIgPT0gMywgImVuc2VtYmxfZ2VuZV9pZCJdCmNsdXN0ZXI0IDwtIG1vc3RfZXhwcmVzc2VkX2dlbmVzX3N0YXRbbW9zdF9leHByZXNzZWRfZ2VuZXNfc3RhdCRjbHVzdGVyID09IDQsICJlbnNlbWJsX2dlbmVfaWQiXQpjbHVzdGVyNSA8LSBtb3N0X2V4cHJlc3NlZF9nZW5lc19zdGF0W21vc3RfZXhwcmVzc2VkX2dlbmVzX3N0YXQkY2x1c3RlciA9PSA1LCAiZW5zZW1ibF9nZW5lX2lkIl0KCmxpc3RfY2x1c3RlcnNfZ2VuZXMgPSBsaXN0KGNsdXN0ZXIxLCBjbHVzdGVyMiwgY2x1c3RlcjMsCiAgICAgICAgICAgICAgICAgICAgICAgICAgIGNsdXN0ZXI0LCBjbHVzdGVyNSkKCmdvc3RfY2x1c3RlcnNfcmVzIDwtIGdvc3QocXVlcnkgPSBsaXN0X2NsdXN0ZXJzX2dlbmVzLAogICAgICAgICAgICAgICAgICAgICAgICAgIG9yZ2FuaXNtID0gIm1tdXNjdWx1cyIsCiAgICAgICAgICAgICAgICAgICAgICAgICAgc2lnbmlmaWNhbnQgPSBUUlVFLCAKICAgICAgICAgICAgICAgICAgICAgICAgICBtdWx0aV9xdWVyeSA9IFRSVUUsCiAgICAgICAgICAgICAgICAgICAgICAgICAgdXNlcl90aHJlc2hvbGQgPSAwLjA1LCAjIGN1c3RvbSBwLXZhbHVlIHRocmVzaG9sZCBmb3Igc2lnbmlmaWNhbmNlCiAgICAgICAgICAgICAgICAgICAgICAgICAgY29ycmVjdGlvbl9tZXRob2QgPSAiZmRyIiwKICAgICAgICAgICAgICAgICAgICAgICAgICBkb21haW5fc2NvcGUgPSAiYW5ub3RhdGVkIiwKICAgICAgICAgICAgICAgICAgICAgICAgICBhc19zaG9ydF9saW5rID0gRkFMU0UpCgpnb3N0cGxvdChnb3N0X2NsdXN0ZXJzX3JlcywgY2FwcGVkID0gVFJVRSwgCiAgICAgICAgIGludGVyYWN0aXZlID0gVFJVRSkKCmBgYAoKUXVlbHF1ZXMgb2JzZXJ2YXRpb25zIDoKCi1MZXMgZ8OobmVzIGR1IGNsdXN0ZXIgMiwgZXhwcmltw6lzIGRhbnMgbGVzIGNvbmRpdGlvbnMgcGV1IGFwcsOocyBsZSB0cmFpdG1lbnQgw6AgbCdhY2lkZSBmb2xpcXVlLCBzb250IHNvdXZlbnQgaW1wbGlxdcOpcyBkYW5zIGxhIHLDqXBvbnNlIGltbXVuaXRhaXJlIGV0IGwnaW5mbGFtYXRpb24sIHByb2JhYmxlbWVudCBwb3VyIHLDqXBvbmRyZSBhdSBjaG9jIHByb2R1aXQgcGFyIGxlIHRyYWl0ZW1lbnQgc3VyIGxlcyByZWlucyBkZSBsYSBzb3VyaXMuIAoKLUxlIGNsdXN0ZXIgNCAoZG9udCBsJ2V4cHJlc3Npb24gYmFpc3NlIGRhbnMgbGVzIGNvbmRpdGlvbnMgcGV1IGFwcsOocyBsZSB0cmFpdGVtZW50IGF2YW50IGRlIHJlbW9udGVyKSBjb21wcmVuZCBkZXMgZ8OobmVzIGltcGxpcXXDqXMgZGFucyBsZSB0cmFuc3BvcnQgdHJhbnNtZW1icmFuYWlyZSwgbGEgZ2x5Y29seXNlLi4uIElsIHJlZmzDqHRlIHByb2JhYmxlbWVudCBsJ2FjdGl2aXTDqSBub3JtYWxlIGRlcyByZWlucy4KCi1MZXMgZ8OobmVzIGR1IGNsdXN0ZXIgMyAoYWN0aXbDqSBkYW5zIGwnw6ljaGFudGlsbG9ucyBvdXRsaWVyIGRheTJfMikgc2VtYmxlbnQgaW1wbGlxdcOpcyBkYW5zIGxhIGRpdmlzaW9uIGNlbGx1bGFpcmUuLi4KCgoKCiMjIENvbmNsdXNpb25zIGfDqW7DqXJhbGVzCgpSw6lzdW1leiBlbiBxdWVscXVlcyBwaHJhc2VzIHZvcyBjb25jbHVzaW9ucyDDoCBwYXJ0aXIgZGVzIHLDqXN1bHRhdHMgb2J0ZW51cy4gCgpEYW5zIGwnYW5hbHlzZSBkJ2VucmljaGlzc2VtZW50IGfDqW7DqXJhbGUgb24gc2VtYmxlIHJldHJvdXZlciBkZSBub21icmV1eCBnw6huZXMgaW1wbGlxdcOpcyBkYW5zIGxhIHLDqXBvbnNlIGltbXVuaXRhaXJlLgoKTCdhbmFseXNlIHBhciBjbHVzdGVyIHRlbmQgw6Agc3VnZ8OpcmVyIHF1ZSBjZXMgZ8OobmVzIHNvbnQgbm90YW1tZW50IGFjdGl2w6lzIGVuIHLDqXBvbnNlIGF1IHRyYWl0ZW1lbnQgw6AgbCdhY2lkZSBmb2xpcXVlIGRhbnMgbGVzIGpvdXJzIDEsIDIsIDMgZXQgNy4gTGV1ciBleHByZXNzaW9uIHJlZGltaW51ZSBlbnN1aXRlIGF1IGpvdXIgMTQuCgpMZXMgZ8OobmVzIHJlZmzDqXRhbnQgbCdhY3Rpdml0w6kgbm9ybWFsZSBkdSByZWluIHNvbnQgYXUgY29udHJhaXJlIG1vaW5zIGV4cHJpbcOpcyBqdXN0ZSBhcHLDqHMgbCdpbmplY3Rpb24gZCdhY2lkZSBmb2xpcXVlIChqb3VycyAxIGV0IDIpIHB1aXMgbGV1ciBleHByZXNzaW9uIGNvbW1lbmNlIMOgIHJlbW9udGVyIGFwcsOocyBsZSBqb3VyIDMuCgpFbiBjb25jbHVzaW9uLCBvbiBvYnNlcnZlIGljaSBsYSBjZXJ0YWluZSByZXZlcnNpYmlsaXTDqSBkdSB0cmFpdGVtZW50IMOgIGwnYWNpZGUgZm9saXF1ZSBzdXIgbGVzIHNvdXJpcy4gTGVzIHJlaW5zIHJldHJvdXZlbnQgdW5lIGV4cHJlc3Npb24gZ8OpbsOpdGlxdWUgcHJvY2hlIGRlIGxhIG5vcm1hbGUuIENlcGVuZGFudCwgc2kgbCdvbiByZWdhcmRlIGxlcyBoZWF0bWFwcywgb24gb2JzZXJ2ZSB1bmUgc291cy1wYXJ0aWUgZHUgY2x1c3RlciBkZSBnw6huZXMgMSBkb250IGwnZXhwcmVzc2lvbiBlc3QgcGx1cyBoYXV0ZSBxdWUgbGEgbm9ybWFsZSBhdXggam91cnMgNyBldCAxNCwgcGV1dC3DqnRyZSBxdWUgY2V1eCBjaSByZWZsZXRlbnQgZGVzIHPDqXF1ZWxsZXMgcGx1cyBkdXJhYmxlcyBkdSB0cmFpdGVtZW50IHN1ciBsZSByZWluID8KCgoKIyMgU2Vzc2lvbiBpbmZvCgpgYGB7ciBzZXNzaW9uX2luZm99CiMjIyMgU2Vzc2lvbiBpbmZvICMjIyMKc2Vzc2lvbkluZm8oKQoKYGBgCg==